public class

MediaUtils

extends java.lang.Object

 java.lang.Object

↳androidx.media2.MediaUtils

Gradle dependencies

compile group: 'androidx.media2', name: 'media2', version: '1.0.0-alpha04'

  • groupId: androidx.media2
  • artifactId: media2
  • version: 1.0.0-alpha04

Artifact androidx.media2:media2:1.0.0-alpha04 it located at Google repository (https://maven.google.com/)

Androidx artifact mapping:

androidx.media2:media2 com.android.support:media2

Summary

Fields
public static final intCURRENT_VERSION

public static final java.util.concurrent.ExecutorDIRECT_EXECUTOR

public static final MediaBrowserServiceCompat.BrowserRootsDefaultBrowserRoot

public static final java.lang.StringTAG

public static final intTRANSACTION_SIZE_LIMIT_IN_BYTES

public static final intVERSION_0

Methods
public static java.util.List<ParcelImpl>convertCommandButtonListToParcelImplList(java.util.List<MediaSession.CommandButton> commandButtonList)

Convert a list of MediaSession.CommandButton to a list of ParcelImpl.

public static java.util.List<MediaItem>convertMediaItemListToMediaItemList(java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem> items)

Convert a list of to a list of MediaItem.

public static ParcelImplListSliceconvertMediaItemListToParcelImplListSlice(java.util.List<MediaItem> mediaItemList)

Convert a list of MediaItem to a list of ParcelImplListSlice.

public static java.util.List<MediaItem>convertParcelImplListSliceToMediaItemList(ParcelImplListSlice listSlice)

Convert a ParcelImplListSlice to a list of MediaItem.

public static java.util.List<MediaItem>convertQueueItemListToMediaItemList(java.util.List<android.support.v4.media.session.MediaSessionCompat.QueueItem> items)

Convert a list of android.support.v4.media.session.MediaSessionCompat.QueueItem to a list of MediaItem.

public static java.util.List<MediaSession.CommandButton>convertToCustomLayout(android.support.v4.media.session.PlaybackStateCompat state)

Converts android.support.v4.media.session.PlaybackStateCompat.CustomAction in the android.support.v4.media.session.PlaybackStateCompat to the custom layout which is the list of the MediaSession.CommandButton.

public static MediaLibraryService.LibraryParamsconvertToLibraryParams(Context context, Bundle legacyBundle)

Converts the rootHints, option, and extra to the MediaLibraryService.LibraryParams.

public static MediaItemconvertToMediaItem(android.support.v4.media.MediaBrowserCompat.MediaItem item)

Creates a MediaItem from the .

public static MediaItemconvertToMediaItem(android.support.v4.media.MediaDescriptionCompat descriptionCompat)

Convert a android.support.v4.media.MediaDescriptionCompat to a MediaItem.

public static android.support.v4.media.MediaBrowserCompat.MediaItemconvertToMediaItem(MediaItem item2)

Creates a from the MediaItem.

public static MediaItemconvertToMediaItem(android.support.v4.media.MediaMetadataCompat metadataCompat)

Convert a android.support.v4.media.MediaMetadataCompat from the getMetadata to a MediaItem.

public static MediaItemconvertToMediaItem(android.support.v4.media.session.MediaSessionCompat.QueueItem item)

Convert a android.support.v4.media.session.MediaSessionCompat.QueueItem to a MediaItem.

public static java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem>convertToMediaItemList(java.util.List<MediaItem> items)

Convert a list of MediaItem to a list of .

public static MediaMetadataconvertToMediaMetadata(java.lang.CharSequence queueTitle)

Creates a MediaMetadata from the java.lang.CharSequence.

public static android.support.v4.media.MediaMetadataCompatconvertToMediaMetadataCompat(MediaMetadata metadata)

Creates a android.support.v4.media.MediaMetadataCompat from the MediaMetadata.

public static intconvertToPlaybackStateCompatState(int playerState, int bufferingState)

Convert a SessionPlayer.PlayerState and SessionPlayer.BuffState into .

public static intconvertToPlayerState(android.support.v4.media.session.PlaybackStateCompat state)

Convert a android.support.v4.media.session.PlaybackStateCompat into SessionPlayer.PlayerState.

public static java.util.List<android.support.v4.media.session.MediaSessionCompat.QueueItem>convertToQueueItemList(java.util.List<MediaItem> items)

Convert a list of MediaItem to a list of android.support.v4.media.session.MediaSessionCompat.QueueItem.

public static RatingconvertToRating(android.support.v4.media.RatingCompat ratingCompat)

Creates a Rating from the android.support.v4.media.RatingCompat.

public static android.support.v4.media.RatingCompatconvertToRatingCompat(Rating rating)

Creates a android.support.v4.media.RatingCompat from the Rating.

public static BundleconvertToRootHints(MediaLibraryService.LibraryParams params)

Converts MediaLibraryService.LibraryParams to the root hints.

public static SessionCommandGroupconvertToSessionCommandGroup(long sessionFlags, android.support.v4.media.session.PlaybackStateCompat state)

Converts session flags and android.support.v4.media.session.PlaybackStateCompat to the SessionCommandGroup.

public static android.support.v4.media.MediaDescriptionCompatcreateMediaDescriptionCompat(java.lang.String mediaId)

Creates android.support.v4.media.MediaDescriptionCompat with the id

public static VersionedParcelablefromParcelable(ParcelImpl p)

Media2 version of ParcelUtils.fromParcelable(Parcelable).

public static booleanisUnparcelableBundle(Bundle bundle)

Returns whether the bundle is not parcelable.

public static voidkeepUnparcelableBundlesOnly(java.util.List<Bundle> bundles)

Removes unparcelable bundles in the given list.

public static java.util.List<java.lang.Object>removeNullElements(java.util.List<java.lang.Object> list)

Removes all null elements from the list and returns it.

public static inttoBufferingState(int playbackStateCompatState)

Convert a into SessionPlayer.BuffState.

public static ParcelImpltoParcelable(VersionedParcelable item)

Media2 version of ParcelUtils.toParcelable(VersionedParcelable).

public static MediaController.PlaybackInfotoPlaybackInfo2(android.support.v4.media.session.MediaControllerCompat.PlaybackInfo info)

Convert a into MediaController.PlaybackInfo.

public static java.util.List<Parcelable>truncateListBySize(java.util.List<Parcelable> list, int sizeLimitInBytes)

Return a list which consists of first N items of the given list with the same order.

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Fields

public static final java.lang.String TAG

public static final int TRANSACTION_SIZE_LIMIT_IN_BYTES

public static final MediaBrowserServiceCompat.BrowserRoot sDefaultBrowserRoot

public static final java.util.concurrent.Executor DIRECT_EXECUTOR

public static final int VERSION_0

public static final int CURRENT_VERSION

Methods

public static android.support.v4.media.MediaBrowserCompat.MediaItem convertToMediaItem(MediaItem item2)

Creates a from the MediaItem.

Parameters:

item2: an item.

Returns:

The newly created media item.

public static java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem> convertToMediaItemList(java.util.List<MediaItem> items)

Convert a list of MediaItem to a list of .

public static MediaItem convertToMediaItem(android.support.v4.media.MediaBrowserCompat.MediaItem item)

Creates a MediaItem from the .

Parameters:

item: an item.

Returns:

The newly created media item.

public static MediaItem convertToMediaItem(android.support.v4.media.session.MediaSessionCompat.QueueItem item)

Convert a android.support.v4.media.session.MediaSessionCompat.QueueItem to a MediaItem.

public static MediaItem convertToMediaItem(android.support.v4.media.MediaMetadataCompat metadataCompat)

Convert a android.support.v4.media.MediaMetadataCompat from the getMetadata to a MediaItem.

public static MediaItem convertToMediaItem(android.support.v4.media.MediaDescriptionCompat descriptionCompat)

Convert a android.support.v4.media.MediaDescriptionCompat to a MediaItem.

public static java.util.List<MediaItem> convertMediaItemListToMediaItemList(java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem> items)

Convert a list of to a list of MediaItem.

public static java.util.List<MediaItem> convertQueueItemListToMediaItemList(java.util.List<android.support.v4.media.session.MediaSessionCompat.QueueItem> items)

Convert a list of android.support.v4.media.session.MediaSessionCompat.QueueItem to a list of MediaItem.

public static android.support.v4.media.MediaDescriptionCompat createMediaDescriptionCompat(java.lang.String mediaId)

Creates android.support.v4.media.MediaDescriptionCompat with the id

public static java.util.List<android.support.v4.media.session.MediaSessionCompat.QueueItem> convertToQueueItemList(java.util.List<MediaItem> items)

Convert a list of MediaItem to a list of android.support.v4.media.session.MediaSessionCompat.QueueItem. The index of the item would be used as the queue ID to match the behavior of MediaController.

public static java.util.List<MediaItem> convertParcelImplListSliceToMediaItemList(ParcelImplListSlice listSlice)

Convert a ParcelImplListSlice to a list of MediaItem.

public static java.util.List<Parcelable> truncateListBySize(java.util.List<Parcelable> list, int sizeLimitInBytes)

Return a list which consists of first N items of the given list with the same order. N is determined as the maximum number of items whose total parcelled size is less than sizeLimitInBytes.

public static MediaMetadata convertToMediaMetadata(java.lang.CharSequence queueTitle)

Creates a MediaMetadata from the java.lang.CharSequence.

public static android.support.v4.media.MediaMetadataCompat convertToMediaMetadataCompat(MediaMetadata metadata)

Creates a android.support.v4.media.MediaMetadataCompat from the MediaMetadata.

Parameters:

metadata: A MediaMetadata object.

Returns:

The newly created android.support.v4.media.MediaMetadataCompat object.

public static Rating convertToRating(android.support.v4.media.RatingCompat ratingCompat)

Creates a Rating from the android.support.v4.media.RatingCompat.

Parameters:

ratingCompat: A android.support.v4.media.RatingCompat object.

Returns:

The newly created Rating object.

public static android.support.v4.media.RatingCompat convertToRatingCompat(Rating rating)

Creates a android.support.v4.media.RatingCompat from the Rating.

Parameters:

rating: A Rating object.

Returns:

The newly created android.support.v4.media.RatingCompat object.

public static java.util.List<ParcelImpl> convertCommandButtonListToParcelImplList(java.util.List<MediaSession.CommandButton> commandButtonList)

Convert a list of MediaSession.CommandButton to a list of ParcelImpl.

public static ParcelImplListSlice convertMediaItemListToParcelImplListSlice(java.util.List<MediaItem> mediaItemList)

Convert a list of MediaItem to a list of ParcelImplListSlice.

public static int convertToPlaybackStateCompatState(int playerState, int bufferingState)

Convert a SessionPlayer.PlayerState and SessionPlayer.BuffState into .

public static int convertToPlayerState(android.support.v4.media.session.PlaybackStateCompat state)

Convert a android.support.v4.media.session.PlaybackStateCompat into SessionPlayer.PlayerState.

public static int toBufferingState(int playbackStateCompatState)

Convert a into SessionPlayer.BuffState.

public static MediaController.PlaybackInfo toPlaybackInfo2(android.support.v4.media.session.MediaControllerCompat.PlaybackInfo info)

Convert a into MediaController.PlaybackInfo.

public static boolean isUnparcelableBundle(Bundle bundle)

Returns whether the bundle is not parcelable.

public static void keepUnparcelableBundlesOnly(java.util.List<Bundle> bundles)

Removes unparcelable bundles in the given list.

public static MediaLibraryService.LibraryParams convertToLibraryParams(Context context, Bundle legacyBundle)

Converts the rootHints, option, and extra to the MediaLibraryService.LibraryParams.

Parameters:

legacyBundle:

Returns:

new LibraryParams

public static Bundle convertToRootHints(MediaLibraryService.LibraryParams params)

Converts MediaLibraryService.LibraryParams to the root hints.

Parameters:

params:

Returns:

new root hints

public static java.util.List<java.lang.Object> removeNullElements(java.util.List<java.lang.Object> list)

Removes all null elements from the list and returns it.

Parameters:

list:

Returns:

public static SessionCommandGroup convertToSessionCommandGroup(long sessionFlags, android.support.v4.media.session.PlaybackStateCompat state)

Converts session flags and android.support.v4.media.session.PlaybackStateCompat to the SessionCommandGroup.

This ignores actions in the android.support.v4.media.session.PlaybackStateCompat to workaround media apps' issues that they don't set playback state correctly.

Parameters:

sessionFlags: session flag
state: playback state

Returns:

the converted session command group

public static java.util.List<MediaSession.CommandButton> convertToCustomLayout(android.support.v4.media.session.PlaybackStateCompat state)

Converts android.support.v4.media.session.PlaybackStateCompat.CustomAction in the android.support.v4.media.session.PlaybackStateCompat to the custom layout which is the list of the MediaSession.CommandButton.

Parameters:

state: playback state

Returns:

custom layout. Always non-null.

public static ParcelImpl toParcelable(VersionedParcelable item)

Media2 version of ParcelUtils.toParcelable(VersionedParcelable).

This sanitizes MediaItem's subclass information.

Parameters:

item:

Returns:

public static VersionedParcelable fromParcelable(ParcelImpl p)

Media2 version of ParcelUtils.fromParcelable(Parcelable).

Source

/*
 * Copyright 2018 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.media2;

import static android.support.v4.media.MediaDescriptionCompat.EXTRA_BT_FOLDER_TYPE;
import static android.support.v4.media.session.MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS;

import static androidx.media2.MediaMetadata.BROWSABLE_TYPE_MIXED;
import static androidx.media2.MediaMetadata.BROWSABLE_TYPE_NONE;
import static androidx.media2.MediaMetadata.METADATA_KEY_ADVERTISEMENT;
import static androidx.media2.MediaMetadata.METADATA_KEY_BROWSABLE;
import static androidx.media2.MediaMetadata.METADATA_KEY_DISPLAY_DESCRIPTION;
import static androidx.media2.MediaMetadata.METADATA_KEY_DISPLAY_ICON;
import static androidx.media2.MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI;
import static androidx.media2.MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE;
import static androidx.media2.MediaMetadata.METADATA_KEY_DISPLAY_TITLE;
import static androidx.media2.MediaMetadata.METADATA_KEY_DOWNLOAD_STATUS;
import static androidx.media2.MediaMetadata.METADATA_KEY_EXTRAS;
import static androidx.media2.MediaMetadata.METADATA_KEY_MEDIA_ID;
import static androidx.media2.MediaMetadata.METADATA_KEY_MEDIA_URI;
import static androidx.media2.MediaMetadata.METADATA_KEY_PLAYABLE;
import static androidx.media2.MediaMetadata.METADATA_KEY_TITLE;
import static androidx.media2.SessionCommand.COMMAND_CODE_PLAYER_SET_SPEED;
import static androidx.media2.SessionCommand.COMMAND_VERSION_CURRENT;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
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.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.annotation.VisibleForTesting;
import androidx.media.AudioAttributesCompat;
import androidx.media.MediaBrowserServiceCompat.BrowserRoot;
import androidx.media2.MediaLibraryService.LibraryParams;
import androidx.media2.MediaSession.CommandButton;
import androidx.versionedparcelable.ParcelImpl;
import androidx.versionedparcelable.ParcelUtils;
import androidx.versionedparcelable.VersionedParcelable;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor;

/**
 * @hide
 */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
public class MediaUtils {
    public static final String TAG = "MediaUtils";
    public static final int TRANSACTION_SIZE_LIMIT_IN_BYTES = 256 * 1024; // 256KB

    // Stub BrowserRoot for accepting any connection here.
    public static final BrowserRoot sDefaultBrowserRoot =
            new BrowserRoot(MediaLibraryService.SERVICE_INTERFACE, null);

    public static final Executor DIRECT_EXECUTOR = new Executor() {
        @Override
        public void execute(Runnable command) {
            command.run();
        }
    };

    // Initial version for all Media2 APIs.
    public static final int VERSION_0 = 0;

    // Current version for all Media2 APIs.
    public static final int CURRENT_VERSION = VERSION_0;

    private static final Map<String, String> METADATA_COMPAT_KEY_TO_METADATA_KEY = new HashMap<>();
    private static final Map<String, String> METADATA_KEY_TO_METADATA_COMPAT_KEY = new HashMap<>();
    static {
        METADATA_COMPAT_KEY_TO_METADATA_KEY.put(
                MediaMetadataCompat.METADATA_KEY_ADVERTISEMENT, METADATA_KEY_ADVERTISEMENT);
        METADATA_COMPAT_KEY_TO_METADATA_KEY.put(MediaMetadataCompat.METADATA_KEY_BT_FOLDER_TYPE,
                METADATA_KEY_BROWSABLE);
        METADATA_COMPAT_KEY_TO_METADATA_KEY.put(MediaMetadataCompat.METADATA_KEY_DOWNLOAD_STATUS,
                METADATA_KEY_DOWNLOAD_STATUS);

        // Invert METADATA_COMPAT_KEY_TO_METADATA_KEY to create METADATA_KEY_TO_METADATA_COMPAT_KEY.
        for (Map.Entry<String, String> entry : METADATA_COMPAT_KEY_TO_METADATA_KEY.entrySet()) {
            // Sanity check..
            if (METADATA_KEY_TO_METADATA_COMPAT_KEY.containsKey(entry.getValue())) {
                throw new RuntimeException("Shouldn't map to the same value");
            }
            METADATA_KEY_TO_METADATA_COMPAT_KEY.put(entry.getValue(), entry.getKey());
        }
    }

    private MediaUtils() {
    }

    /**
     * Creates a {@link MediaBrowserCompat.MediaItem} from the {@link MediaItem}.
     *
     * @param item2 an item.
     * @return The newly created media item.
     */
    public static MediaBrowserCompat.MediaItem convertToMediaItem(MediaItem item2) {
        if (item2 == null) {
            return null;
        }
        int flags = 0;
        MediaDescriptionCompat descCompat;
        MediaMetadata metadata = item2.getMetadata();
        if (metadata == null) {
            descCompat = new MediaDescriptionCompat.Builder()
                    .setMediaId(item2.getMediaId())
                    .build();
        } else {
            MediaDescriptionCompat.Builder builder = new MediaDescriptionCompat.Builder()
                    .setMediaId(item2.getMediaId())
                    .setSubtitle(metadata.getText(METADATA_KEY_DISPLAY_SUBTITLE))
                    .setDescription(metadata.getText(METADATA_KEY_DISPLAY_DESCRIPTION))
                    .setIconBitmap(metadata.getBitmap(METADATA_KEY_DISPLAY_ICON))
                    .setExtras(metadata.getExtras());

            String title = metadata.getString(METADATA_KEY_TITLE);
            if (title != null) {
                builder.setTitle(title);
            } else {
                builder.setTitle(metadata.getString(METADATA_KEY_DISPLAY_TITLE));
            }

            String displayIconUri = metadata.getString(METADATA_KEY_DISPLAY_ICON_URI);
            if (displayIconUri != null) {
                builder.setIconUri(Uri.parse(displayIconUri));
            }

            String mediaUri = metadata.getString(METADATA_KEY_MEDIA_URI);
            if (mediaUri != null) {
                builder.setMediaUri(Uri.parse(mediaUri));
            }

            descCompat = builder.build();

            boolean browsable = metadata.containsKey(METADATA_KEY_BROWSABLE)
                    && metadata.getLong(METADATA_KEY_BROWSABLE) != BROWSABLE_TYPE_NONE;
            boolean playable = metadata.getLong(METADATA_KEY_PLAYABLE) != 0;
            flags = (browsable ? MediaBrowserCompat.MediaItem.FLAG_BROWSABLE : 0)
                    | (playable ? MediaBrowserCompat.MediaItem.FLAG_PLAYABLE : 0);
        }
        return new MediaBrowserCompat.MediaItem(descCompat, flags);
    }

    /**
     * Convert a list of {@link MediaItem} to a list of {@link MediaBrowserCompat.MediaItem}.
     */
    public static List<MediaBrowserCompat.MediaItem> convertToMediaItemList(List<MediaItem> items) {
        if (items == null) {
            return null;
        }
        List<MediaBrowserCompat.MediaItem> result = new ArrayList<>();
        for (int i = 0; i < items.size(); i++) {
            result.add(convertToMediaItem(items.get(i)));
        }
        return result;
    }

    /**
     * Creates a {@link MediaItem} from the {@link MediaBrowserCompat.MediaItem}.
     *
     * @param item an item.
     * @return The newly created media item.
     */
    public static MediaItem convertToMediaItem(MediaBrowserCompat.MediaItem item) {
        if (item == null) {
            return null;
        }
        MediaMetadata metadata = convertToMediaMetadata(item.getDescription(),
                item.isBrowsable(), item.isPlayable());
        return new MediaItem.Builder()
                .setMetadata(metadata)
                .build();
    }

    /**
     * Convert a {@link QueueItem} to a {@link MediaItem}.
     */
    public static MediaItem convertToMediaItem(QueueItem item) {
        if (item == null) {
            return null;
        }
        // descriptionCompat cannot be null
        MediaDescriptionCompat descriptionCompat = item.getDescription();
        MediaMetadata metadata = convertToMediaMetadata(descriptionCompat, false, true);
        return new MediaItem.Builder()
                .setMetadata(metadata)
                .build();
    }

    /**
     * Convert a {@link MediaMetadataCompat} from the {@link MediaControllerCompat#getMetadata()}
     * to a {@link MediaItem}.
     */
    public static MediaItem convertToMediaItem(MediaMetadataCompat metadataCompat) {
        if (metadataCompat == null) {
            return null;
        }
        // Item is from the MediaControllerCompat, so forcefully set the playable.
        MediaMetadata.Builder builder = new MediaMetadata.Builder()
                .putLong(METADATA_KEY_PLAYABLE, 1);
        for (String key : metadataCompat.keySet()) {
            Object value = metadataCompat.getBundle().get(key);
            String metadataKey = METADATA_COMPAT_KEY_TO_METADATA_KEY.containsKey(key)
                    ? METADATA_COMPAT_KEY_TO_METADATA_KEY.get(key) : key;
            if (value instanceof CharSequence) {
                builder.putText(metadataKey, (CharSequence) value);
            } else if (value instanceof Bitmap) {
                builder.putBitmap(metadataKey, (Bitmap) value);
            } else if (value instanceof Long) {
                builder.putLong(metadataKey, (Long) value);
            } else if ((value instanceof RatingCompat)
                    || (Build.VERSION.SDK_INT >= 19 && value instanceof android.media.Rating)) {
                // Must be fwk Rating or RatingCompat according to SDK versions.
                // Use MediaMetadataCompat#getRating(key) to get a RatingCompat object.
                try {
                    RatingCompat rating = metadataCompat.getRating(key);
                    builder.putRating(metadataKey, MediaUtils.convertToRating(rating));
                } catch (Exception e) {
                    // Prevent from CastException in the getRating() due to the future changes.
                }
            }
        }
        return new MediaItem.Builder().setMetadata(builder.build()).build();
    }

    /**
     * Convert a {@link MediaDescriptionCompat} to a {@link MediaItem}.
     */
    public static MediaItem convertToMediaItem(MediaDescriptionCompat descriptionCompat) {
        MediaMetadata metadata = convertToMediaMetadata(descriptionCompat, false, true);
        if (metadata == null) {
            return null;
        }
        return new MediaItem.Builder().setMetadata(metadata).build();
    }

    /**
     * Convert a list of {@link MediaBrowserCompat.MediaItem} to a list of {@link MediaItem}.
     */
    public static List<MediaItem> convertMediaItemListToMediaItemList(
            List<MediaBrowserCompat.MediaItem> items) {
        if (items == null) {
            return null;
        }
        List<MediaItem> result = new ArrayList<>();
        for (int i = 0; i < items.size(); i++) {
            result.add(convertToMediaItem(items.get(i)));
        }
        return result;
    }

    /**
     * Convert a list of {@link QueueItem} to a list of {@link MediaItem}.
     */
    public static List<MediaItem> convertQueueItemListToMediaItemList(List<QueueItem> items) {
        if (items == null) {
            return null;
        }
        List<MediaItem> result = new ArrayList<>();
        for (int i = 0; i < items.size(); i++) {
            MediaItem item = convertToMediaItem(items.get(i));
            if (item != null) {
                result.add(item);
            }
        }
        return result;
    }

    /**
     * Creates {@link MediaDescriptionCompat} with the id
     */
    public static MediaDescriptionCompat createMediaDescriptionCompat(String mediaId) {
        if (TextUtils.isEmpty(mediaId)) {
            return null;
        }
        return new MediaDescriptionCompat.Builder().setMediaId(mediaId).build();
    }

    /**
     * Convert a list of {@link MediaItem} to a list of {@link QueueItem}. The index of the item
     * would be used as the queue ID to match the behavior of {@link MediaController}.
     */
    public static List<QueueItem> convertToQueueItemList(List<MediaItem> items) {
        if (items == null) {
            return null;
        }
        List<QueueItem> result = new ArrayList<>();
        for (int i = 0; i < items.size(); i++) {
            MediaItem item = items.get(i);
            MediaDescriptionCompat description = (item.getMetadata() == null)
                    ? new MediaDescriptionCompat.Builder().setMediaId(item.getMediaId()).build()
                    : convertToMediaMetadataCompat(item.getMetadata()).getDescription();
            result.add(new QueueItem(description, i));
        }
        return result;
    }

    /**
     * Convert a {@link ParcelImplListSlice} to a list of {@link MediaItem}.
     */
    public static List<MediaItem> convertParcelImplListSliceToMediaItemList(
            ParcelImplListSlice listSlice) {
        if (listSlice == null) {
            return null;
        }
        List<ParcelImpl> parcelImplList = listSlice.getList();
        List<MediaItem> mediaItemList = new ArrayList<>();
        for (int i = 0; i < parcelImplList.size(); i++) {
            final ParcelImpl itemParcelImpl = parcelImplList.get(i);
            if (itemParcelImpl != null) {
                mediaItemList.add((MediaItem) fromParcelable(itemParcelImpl));
            }
        }
        return mediaItemList;
    }

    /**
     * Return a list which consists of first {@code N} items of the given list with the same order.
     * {@code N} is determined as the maximum number of items whose total parcelled size is less
     * than {@param sizeLimitInBytes}.
     */
    public static <T extends Parcelable> List<T> truncateListBySize(final List<T> list,
            final int sizeLimitInBytes) {
        if (list == null) {
            return null;
        }
        List<T> result = new ArrayList<>();
        Parcel parcel = Parcel.obtain();
        for (int i = 0; i < list.size(); i++) {
            // Calculate the size.
            T item = list.get(i);
            parcel.writeParcelable(item, 0);
            if (parcel.dataSize() < sizeLimitInBytes) {
                result.add(item);
            } else {
                break;
            }
        }
        parcel.recycle();
        return result;
    }

    /**
     * Creates a {@link MediaMetadata} from the {@link MediaDescriptionCompat}.
     *
     * @param descCompat A {@link MediaDescriptionCompat} object.
     * @param browsable {@code true} if it's from {@link MediaBrowserCompat.MediaItem} with
     *                  browsable flag.
     * @param playable {@code true} if it's from {@link MediaBrowserCompat.MediaItem} with
     *                  playable flag, or from {@link QueueItem}.
     * @return
     */
    private static MediaMetadata convertToMediaMetadata(MediaDescriptionCompat descCompat,
            boolean browsable, boolean playable) {
        if (descCompat == null) {
            return null;
        }

        MediaMetadata.Builder metadataBuilder = new MediaMetadata.Builder();
        metadataBuilder.putString(METADATA_KEY_MEDIA_ID, descCompat.getMediaId());

        CharSequence title = descCompat.getTitle();
        if (title != null) {
            metadataBuilder.putText(METADATA_KEY_DISPLAY_TITLE, title);
        }

        CharSequence description = descCompat.getDescription();
        if (description != null) {
            metadataBuilder.putText(METADATA_KEY_DISPLAY_DESCRIPTION, descCompat.getDescription());
        }

        CharSequence subtitle = descCompat.getSubtitle();
        if (subtitle != null) {
            metadataBuilder.putText(METADATA_KEY_DISPLAY_SUBTITLE, subtitle);
        }

        Bitmap icon = descCompat.getIconBitmap();
        if (icon != null) {
            metadataBuilder.putBitmap(METADATA_KEY_DISPLAY_ICON, icon);
        }

        Uri iconUri = descCompat.getIconUri();
        if (iconUri != null) {
            metadataBuilder.putText(METADATA_KEY_DISPLAY_ICON_URI, iconUri.toString());
        }

        Bundle bundle = descCompat.getExtras();
        if (bundle != null) {
            metadataBuilder.setExtras(bundle);
        }

        Uri mediaUri = descCompat.getMediaUri();
        if (mediaUri != null) {
            metadataBuilder.putText(METADATA_KEY_MEDIA_URI, mediaUri.toString());
        }

        if (bundle != null && bundle.containsKey(EXTRA_BT_FOLDER_TYPE)) {
            metadataBuilder.putLong(METADATA_KEY_BROWSABLE,
                    bundle.getLong(EXTRA_BT_FOLDER_TYPE));
        } else if (browsable) {
            metadataBuilder.putLong(METADATA_KEY_BROWSABLE, BROWSABLE_TYPE_MIXED);
        } else {
            metadataBuilder.putLong(METADATA_KEY_BROWSABLE, BROWSABLE_TYPE_NONE);
        }

        metadataBuilder.putLong(METADATA_KEY_PLAYABLE, playable ? 1 : 0);

        return metadataBuilder.build();
    }

    /**
     * Creates a {@link MediaMetadata} from the {@link CharSequence}.
     */
    public static MediaMetadata convertToMediaMetadata(CharSequence queueTitle) {
        if (queueTitle == null) {
            return null;
        }
        return new MediaMetadata.Builder()
                .putString(METADATA_KEY_TITLE, queueTitle.toString())
                .putLong(METADATA_KEY_BROWSABLE, BROWSABLE_TYPE_MIXED)
                .putLong(METADATA_KEY_PLAYABLE, 1)
                .build();
    }

    /**
     * Creates a {@link MediaMetadataCompat} from the {@link MediaMetadata}.
     *
     * @param metadata A {@link MediaMetadata} object.
     * @return The newly created {@link MediaMetadataCompat} object.
     */
    public static MediaMetadataCompat convertToMediaMetadataCompat(MediaMetadata metadata) {
        if (metadata == null) {
            return null;
        }
        MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder();
        for (String key : metadata.keySet()) {
            String compatKey = METADATA_KEY_TO_METADATA_COMPAT_KEY.containsKey(key)
                    ? METADATA_KEY_TO_METADATA_COMPAT_KEY.get(key) : key;
            Object value = metadata.getObject(key);
            if (value instanceof CharSequence) {
                builder.putText(compatKey, (CharSequence) value);
            } else if (value instanceof Bitmap) {
                builder.putBitmap(compatKey, (Bitmap) value);
            } else if (value instanceof Long) {
                builder.putLong(compatKey, (Long) value);
            } else if (value instanceof Bundle && !TextUtils.equals(key, METADATA_KEY_EXTRAS)) {
                // Must be Bundle which contains a Rating.
                // Use MediaMetadata#getRating(key) to get a Rating object.
                try {
                    Rating rating = metadata.getRating(key);
                    builder.putRating(compatKey, MediaUtils.convertToRatingCompat(rating));
                } catch (Exception e) {
                    // Prevent from CastException in the getRating() due to the future changes.
                }
            }
        }
        return builder.build();
    }

    /**
     * Creates a {@link Rating} from the {@link RatingCompat}.
     *
     * @param ratingCompat A {@link RatingCompat} object.
     * @return The newly created {@link Rating} object.
     */
    public static Rating convertToRating(RatingCompat ratingCompat) {
        if (ratingCompat == null) {
            return null;
        }
        switch (ratingCompat.getRatingStyle()) {
            case RatingCompat.RATING_3_STARS:
                return ratingCompat.isRated()
                        ? new StarRating(3, ratingCompat.getStarRating()) : new StarRating(3);
            case RatingCompat.RATING_4_STARS:
                return ratingCompat.isRated()
                        ? new StarRating(4, ratingCompat.getStarRating()) : new StarRating(4);
            case RatingCompat.RATING_5_STARS:
                return ratingCompat.isRated()
                        ? new StarRating(5, ratingCompat.getStarRating()) : new StarRating(5);
            case RatingCompat.RATING_HEART:
                return ratingCompat.isRated()
                        ? new HeartRating(ratingCompat.hasHeart()) : new HeartRating();
            case RatingCompat.RATING_THUMB_UP_DOWN:
                return ratingCompat.isRated()
                        ? new ThumbRating(ratingCompat.isThumbUp()) : new ThumbRating();
            case RatingCompat.RATING_PERCENTAGE:
                return ratingCompat.isRated()
                        ? new PercentageRating(ratingCompat.getPercentRating())
                        : new PercentageRating();
            default:
                return null;
        }
    }

    /**
     * Creates a {@link RatingCompat} from the {@link Rating}.
     *
     * @param rating A {@link Rating} object.
     * @return The newly created {@link RatingCompat} object.
     */
    @SuppressLint("WrongConstant") // for @StarStyle
    public static RatingCompat convertToRatingCompat(Rating rating) {
        if (rating == null) {
            return null;
        }
        int ratingCompatStyle = getRatingCompatStyle(rating);
        if (!rating.isRated()) {
            return RatingCompat.newUnratedRating(ratingCompatStyle);
        }

        switch (ratingCompatStyle) {
            case RatingCompat.RATING_3_STARS:
            case RatingCompat.RATING_4_STARS:
            case RatingCompat.RATING_5_STARS:
                return RatingCompat.newStarRating(
                        ratingCompatStyle, ((StarRating) rating).getStarRating());
            case RatingCompat.RATING_HEART:
                return RatingCompat.newHeartRating(((HeartRating) rating).hasHeart());
            case RatingCompat.RATING_THUMB_UP_DOWN:
                return RatingCompat.newThumbRating(((ThumbRating) rating).isThumbUp());
            case RatingCompat.RATING_PERCENTAGE:
                return RatingCompat.newPercentageRating(
                        ((PercentageRating) rating).getPercentRating());
            default:
                return null;
        }
    }

    /**
     * Convert a list of {@link CommandButton} to a list of {@link ParcelImpl}.
     */
    public static List<ParcelImpl> convertCommandButtonListToParcelImplList(
            List<CommandButton> commandButtonList) {
        if (commandButtonList == null) {
            return null;
        }
        List<ParcelImpl> parcelImplList = new ArrayList<>();
        for (int i = 0; i < commandButtonList.size(); i++) {
            final CommandButton commandButton = commandButtonList.get(i);
            parcelImplList.add(toParcelable(commandButton));
        }
        return parcelImplList;
    }

    /**
     * Convert a list of {@link MediaItem} to a list of {@link ParcelImplListSlice}.
     */
    public static ParcelImplListSlice convertMediaItemListToParcelImplListSlice(
            List<MediaItem> mediaItemList) {
        if (mediaItemList == null) {
            return null;
        }
        List<ParcelImpl> itemParcelableList = new ArrayList<>();
        for (int i = 0; i < mediaItemList.size(); i++) {
            final MediaItem item = mediaItemList.get(i);
            if (item != null) {
                final ParcelImpl itemParcelImpl = toParcelable(item);
                itemParcelableList.add(itemParcelImpl);
            }
        }
        return new ParcelImplListSlice(itemParcelableList);
    }

    /**
     * Convert a {@link SessionPlayer.PlayerState} and
     * {@link SessionPlayer.BuffState} into {@link PlaybackStateCompat.State}.
     */
    public static int convertToPlaybackStateCompatState(int playerState, int bufferingState) {
        switch (playerState) {
            case SessionPlayer.PLAYER_STATE_PLAYING:
                switch (bufferingState) {
                    case SessionPlayer.BUFFERING_STATE_BUFFERING_AND_STARVED:
                        return PlaybackStateCompat.STATE_BUFFERING;
                }
                return PlaybackStateCompat.STATE_PLAYING;
            case SessionPlayer.PLAYER_STATE_PAUSED:
                return PlaybackStateCompat.STATE_PAUSED;
            case SessionPlayer.PLAYER_STATE_IDLE:
                return PlaybackStateCompat.STATE_NONE;
            case SessionPlayer.PLAYER_STATE_ERROR:
                return PlaybackStateCompat.STATE_ERROR;
        }
        // For unknown value
        return PlaybackStateCompat.STATE_ERROR;
    }

    /**
     * Convert a {@link PlaybackStateCompat} into {@link SessionPlayer.PlayerState}.
     */
    public static int convertToPlayerState(PlaybackStateCompat state) {
        if (state == null) {
            return SessionPlayer.PLAYER_STATE_IDLE;
        }
        switch (state.getState()) {
            case PlaybackStateCompat.STATE_ERROR:
                return SessionPlayer.PLAYER_STATE_ERROR;
            case PlaybackStateCompat.STATE_NONE:
                return SessionPlayer.PLAYER_STATE_IDLE;
            case PlaybackStateCompat.STATE_PAUSED:
            case PlaybackStateCompat.STATE_STOPPED:
            case PlaybackStateCompat.STATE_BUFFERING: // means paused for buffering.
                return SessionPlayer.PLAYER_STATE_PAUSED;
            case PlaybackStateCompat.STATE_FAST_FORWARDING:
            case PlaybackStateCompat.STATE_PLAYING:
            case PlaybackStateCompat.STATE_REWINDING:
            case PlaybackStateCompat.STATE_SKIPPING_TO_NEXT:
            case PlaybackStateCompat.STATE_SKIPPING_TO_PREVIOUS:
            case PlaybackStateCompat.STATE_SKIPPING_TO_QUEUE_ITEM:
            case PlaybackStateCompat.STATE_CONNECTING: // Note: there's no perfect match for this.
                return SessionPlayer.PLAYER_STATE_PLAYING;
        }
        return SessionPlayer.PLAYER_STATE_ERROR;
    }

    /**
     * Convert a {@link PlaybackStateCompat.State} into {@link SessionPlayer.BuffState}.
     */
    // Note: there's no perfect match for this.
    public static int toBufferingState(int playbackStateCompatState) {
        switch (playbackStateCompatState) {
            case PlaybackStateCompat.STATE_BUFFERING:
                return SessionPlayer.BUFFERING_STATE_BUFFERING_AND_STARVED;
            case PlaybackStateCompat.STATE_PLAYING:
                return SessionPlayer.BUFFERING_STATE_COMPLETE;
            default:
                return SessionPlayer.BUFFERING_STATE_UNKNOWN;
        }
    }

    /**
     * Convert a {@link MediaControllerCompat.PlaybackInfo} into
     * {@link MediaController.PlaybackInfo}.
     */
    public static MediaController.PlaybackInfo toPlaybackInfo2(
            MediaControllerCompat.PlaybackInfo info) {
        return MediaController.PlaybackInfo.createPlaybackInfo(info.getPlaybackType(),
                new AudioAttributesCompat.Builder()
                        .setLegacyStreamType(info.getAudioStream()).build(),
                info.getVolumeControl(), info.getMaxVolume(), info.getCurrentVolume());
    }

    /**
     * Returns whether the bundle is not parcelable.
     */
    public static boolean isUnparcelableBundle(Bundle bundle) {
        if (bundle == null) {
            return false;
        }
        bundle.setClassLoader(MediaUtils.class.getClassLoader());
        try {
            bundle.size();
        } catch (Exception e) {
            return true;
        }
        return false;
    }

    /**
     * Removes unparcelable bundles in the given list.
     */
    public static void keepUnparcelableBundlesOnly(final List<Bundle> bundles) {
        if (bundles == null) {
            return;
        }
        for (int i = bundles.size() - 1; i >= 0; --i) {
            Bundle bundle = bundles.get(i);
            if (isUnparcelableBundle(bundle)) {
                bundles.remove(i);
            }
        }
    }

    private static @RatingCompat.Style int getRatingCompatStyle(Rating rating) {
        if (rating instanceof HeartRating) {
            return RatingCompat.RATING_HEART;
        } else if (rating instanceof ThumbRating) {
            return RatingCompat.RATING_THUMB_UP_DOWN;
        } else if (rating instanceof StarRating) {
            switch (((StarRating) rating).getMaxStars()) {
                case 3:
                    return RatingCompat.RATING_3_STARS;
                case 4:
                    return RatingCompat.RATING_4_STARS;
                case 5:
                    return RatingCompat.RATING_5_STARS;
            }
        } else if (rating instanceof PercentageRating) {
            return RatingCompat.RATING_PERCENTAGE;
        }
        return RatingCompat.RATING_NONE;
    }

    /**
     * Converts the rootHints, option, and extra to the {@link LibraryParams}.
     *
     * @param legacyBundle
     * @return new LibraryParams
     */
    public static LibraryParams convertToLibraryParams(Context context, Bundle legacyBundle) {
        if (legacyBundle == null) {
            return null;
        }
        try {
            legacyBundle.setClassLoader(context.getClassLoader());
            return new LibraryParams.Builder().setExtras(legacyBundle)
                    .setRecent(legacyBundle.getBoolean(BrowserRoot.EXTRA_RECENT))
                    .setOffline(legacyBundle.getBoolean(BrowserRoot.EXTRA_OFFLINE))
                    .setSuggested(legacyBundle.getBoolean(BrowserRoot.EXTRA_SUGGESTED))
                    .build();
        } catch (Exception e) {
            // Failure when unpacking the legacy bundle.
            return new LibraryParams.Builder().setExtras(legacyBundle).build();
        }
    }

    /**
     * Converts {@link LibraryParams} to the root hints.
     *
     * @param params
     * @return new root hints
     */
    public static Bundle convertToRootHints(LibraryParams params) {
        if (params == null) {
            return null;
        }
        Bundle rootHints = (params.getExtras() == null)
                ? new Bundle() : new Bundle(params.getExtras());
        rootHints.putBoolean(BrowserRoot.EXTRA_RECENT, params.isRecent());
        rootHints.putBoolean(BrowserRoot.EXTRA_OFFLINE, params.isOffline());
        rootHints.putBoolean(BrowserRoot.EXTRA_SUGGESTED, params.isSuggested());
        return rootHints;
    }

    /**
     * Removes all null elements from the list and returns it.
     *
     * @param list
     * @return
     */
    public static <T> List<T> removeNullElements(@Nullable List<T> list) {
        if (list == null) {
            return null;
        }
        List<T> newList = new ArrayList<>();
        for (T item : list) {
            if (item != null) {
                newList.add(item);
            }
        }
        return newList;
    }

    /**
     * Converts {@link MediaControllerCompat#getFlags() session flags} and
     * {@link PlaybackStateCompat} to the {@link SessionCommandGroup}.
     * <p>
     * This ignores {@link PlaybackStateCompat#getActions() actions} in the
     * {@link PlaybackStateCompat} to workaround media apps' issues that they don't set playback
     * state correctly.
     *
     * @param sessionFlags session flag
     * @param state playback state
     * @return the converted session command group
     */
    @NonNull
    public static SessionCommandGroup convertToSessionCommandGroup(long sessionFlags,
            PlaybackStateCompat state) {
        SessionCommandGroup.Builder commandsBuilder = new SessionCommandGroup.Builder();
        boolean includePlaylistCommands = (sessionFlags & FLAG_HANDLES_QUEUE_COMMANDS) != 0;
        commandsBuilder.addAllPlayerCommands(COMMAND_VERSION_CURRENT, includePlaylistCommands);
        commandsBuilder.addAllVolumeCommands(COMMAND_VERSION_CURRENT);
        commandsBuilder.addAllSessionCommands(COMMAND_VERSION_CURRENT);

        commandsBuilder.removeCommand(COMMAND_CODE_PLAYER_SET_SPEED);

        if (state != null && state.getCustomActions() != null) {
            for (CustomAction customAction : state.getCustomActions()) {
                commandsBuilder.addCommand(
                        new SessionCommand(customAction.getAction(), customAction.getExtras()));
            }
        }
        return commandsBuilder.build();
    }

    /**
     * Converts {@link CustomAction} in the {@link PlaybackStateCompat} to the custom layout which
     * is the list of the {@link CommandButton}.
     *
     * @param state playback state
     * @return custom layout. Always non-null.
     */
    @NonNull
    public static List<CommandButton> convertToCustomLayout(PlaybackStateCompat state) {
        List<CommandButton> layout = new ArrayList<>();
        if (state == null) {
            return layout;
        }
        for (CustomAction action : state.getCustomActions()) {
            CommandButton button = new CommandButton.Builder()
                    .setCommand(new SessionCommand(action.getAction(), action.getExtras()))
                    .setDisplayName(action.getName())
                    .setEnabled(true)
                    .setIconResId(action.getIcon()).build();
            layout.add(button);
        }
        return layout;
    }

    /**
     * Media2 version of {@link ParcelUtils#toParcelable(VersionedParcelable)}.
     * <p>
     * This sanitizes {@link MediaItem}'s subclass information.
     *
     * @param item
     * @return
     */
    @SuppressLint("RestrictedApi")
    public static ParcelImpl toParcelable(VersionedParcelable item) {
        if (item instanceof MediaItem) {
            return new MediaItemParcelImpl((MediaItem) item);
        }
        return (ParcelImpl) ParcelUtils.toParcelable(item);
    }

    /**
     * Media2 version of {@link ParcelUtils#fromParcelable(Parcelable)}.
     */
    @SuppressWarnings("TypeParameterUnusedInFormals")
    @SuppressLint("RestrictedApi")
    public static <T extends VersionedParcelable> T fromParcelable(ParcelImpl p) {
        return ParcelUtils.<T>fromParcelable(p);
    }

    private static class MediaItemParcelImpl extends ParcelImpl {
        private final MediaItem mItem;
        @SuppressLint("RestrictedApi")
        MediaItemParcelImpl(MediaItem item) {
            // Up-cast (possibly MediaItem's subclass object) item to MediaItem for the
            // writeToParcel(). The copied media item will be only used when it's sent across the
            // process.
            super(new MediaItem(item));

            // Keeps the original copy for local binder to send the original item.
            // When local binder is used (i.e. binder call happens in a single process),
            // writeToParcel() wouldn't happen for the Parcelable object and the same object will
            // be sent through the binder call.
            mItem = item;
        }

        @Override
        public MediaItem getVersionedParcel() {
            return mItem;
        }
    }
}