Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f60569d5 authored by Sal Savage's avatar Sal Savage
Browse files

Create a builder for audio_util Metadata and migrate off bundles

The Util class in audio_util provides several static utilties for
converting between media framework objects and AVRCP flavored objects.
These conversion methods utilize bundles to do the translations, which
can be limiting when using non-parcelable objects.

While I would love to move off of AVRCP flavored objects for this
generic audio/media interface, that's going to be a meaty change thats
best done when a new version of the media framework is locked in and
supports everything we need. This change serves to make what we have
more flexible in the meantime.

Tag: #refactor
Bug: 153076316
Test: atest BluetoothInstrumentationTests
Change-Id: I0f29291cc4e8c92836bd1777c30dee184ac1b4e8
parent 41e12fc1
Loading
Loading
Loading
Loading
+143 −0
Original line number Diff line number Diff line
@@ -16,6 +16,12 @@

package com.android.bluetooth.audio_util;

import android.media.MediaDescription;
import android.media.MediaMetadata;
import android.media.browse.MediaBrowser.MediaItem;
import android.media.session.MediaSession;
import android.os.Bundle;

import java.util.Objects;

public class Metadata implements Cloneable {
@@ -61,6 +67,143 @@ public class Metadata implements Cloneable {
        return "{ mediaId=\"" + mediaId + "\" title=\"" + title + "\" artist=\"" + artist
                + "\" album=\"" + album + "\" duration=" + duration
                + " trackPosition=" + trackNum + "/" + numTracks + " }";
    }

    /**
     * A Builder object to populate a Metadata from various different Media Framework objects
     */
    public static class Builder {
        private Metadata mMetadata = new Metadata();

        /**
         * Set the Media ID fot the Metadata Object
         */
        public Builder setMediaId(String id) {
            mMetadata.mediaId = id;
            return this;
        }

        /**
         * Extract the fields from a MediaMetadata object into a Metadata, if they exist
         */
        public Builder fromMediaMetadata(MediaMetadata data) {
            if (data == null) return this;

            // First, use the basic description available with the MediaMetadata
            fromMediaDescription(data.getDescription());

            // Then, replace with better data if available on the MediaMetadata
            if (data.containsKey(MediaMetadata.METADATA_KEY_MEDIA_ID)) {
                mMetadata.mediaId = data.getString(MediaMetadata.METADATA_KEY_MEDIA_ID);
            }
            if (data.containsKey(MediaMetadata.METADATA_KEY_TITLE)) {
                mMetadata.title = data.getString(MediaMetadata.METADATA_KEY_TITLE);
            }
            if (data.containsKey(MediaMetadata.METADATA_KEY_ARTIST)) {
                mMetadata.artist = data.getString(MediaMetadata.METADATA_KEY_ARTIST);
            }
            if (data.containsKey(MediaMetadata.METADATA_KEY_ALBUM)) {
                mMetadata.album = data.getString(MediaMetadata.METADATA_KEY_ALBUM);
            }
            if (data.containsKey(MediaMetadata.METADATA_KEY_TRACK_NUMBER)) {
                mMetadata.trackNum = "" + data.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER);
            }
            if (data.containsKey(MediaMetadata.METADATA_KEY_NUM_TRACKS)) {
                mMetadata.numTracks = "" + data.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS);
            }
            if (data.containsKey(MediaMetadata.METADATA_KEY_GENRE)) {
                mMetadata.genre = data.getString(MediaMetadata.METADATA_KEY_GENRE);
            }
            if (data.containsKey(MediaMetadata.METADATA_KEY_DURATION)) {
                mMetadata.duration = "" + data.getLong(MediaMetadata.METADATA_KEY_DURATION);
            }
            return this;
        }

        /**
         * Extract the fields from a MediaItem object into a Metadata, if they exist
         */
        public Builder fromMediaItem(MediaItem item) {
            if (item == null) return this;
            return fromMediaDescription(item.getDescription()).setMediaId(item.getMediaId());
        }

        /**
         * Extract the fields from a MediaDescription object into a Metadata, if they exist
         */
        public Builder fromMediaDescription(MediaDescription desc) {
            if (desc == null) return this;

            // Default the following mapping if they exist
            if (desc.getTitle() != null) mMetadata.title = desc.getTitle().toString();
            if (desc.getSubtitle() != null) mMetadata.artist = desc.getSubtitle().toString();
            if (desc.getDescription() != null) mMetadata.album = desc.getDescription().toString();

            // Then, check the extras in the description for even better data
            return fromBundle(desc.getExtras()).setMediaId(desc.getMediaId());
        }

        /**
         * Extract the fields from a MediaSession.QueueItem object into a Metadata, if they exist
         */
        public Builder fromQueueItem(MediaSession.QueueItem item) {
            if (item == null) return this;
            return fromMediaDescription(item.getDescription());
        }

        /**
         * Extract the fields from a Bundle of MediaMetadata constants into a Metadata, if they
         * exist
         */
        public Builder fromBundle(Bundle bundle) {
            if (bundle == null) return this;
            if (bundle.containsKey(MediaMetadata.METADATA_KEY_MEDIA_ID)) {
                mMetadata.mediaId = bundle.getString(MediaMetadata.METADATA_KEY_MEDIA_ID);
            }
            if (bundle.containsKey(MediaMetadata.METADATA_KEY_TITLE)) {
                mMetadata.title = bundle.getString(MediaMetadata.METADATA_KEY_TITLE);
            }
            if (bundle.containsKey(MediaMetadata.METADATA_KEY_ARTIST)) {
                mMetadata.artist = bundle.getString(MediaMetadata.METADATA_KEY_ARTIST);
            }
            if (bundle.containsKey(MediaMetadata.METADATA_KEY_ALBUM)) {
                mMetadata.album = bundle.getString(MediaMetadata.METADATA_KEY_ALBUM);
            }
            if (bundle.containsKey(MediaMetadata.METADATA_KEY_TRACK_NUMBER)) {
                mMetadata.trackNum = "" + bundle.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER);
            }
            if (bundle.containsKey(MediaMetadata.METADATA_KEY_NUM_TRACKS)) {
                mMetadata.numTracks = "" + bundle.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS);
            }
            if (bundle.containsKey(MediaMetadata.METADATA_KEY_GENRE)) {
                mMetadata.genre = bundle.getString(MediaMetadata.METADATA_KEY_GENRE);
            }
            if (bundle.containsKey(MediaMetadata.METADATA_KEY_DURATION)) {
                mMetadata.duration = "" + bundle.getLong(MediaMetadata.METADATA_KEY_DURATION);
            }
            return this;
        }

        /**
         * Elect to use default values in the Metadata in place of any missing values
         */
        public Builder useDefaults() {
            if (mMetadata.mediaId == null) mMetadata.mediaId = "Not Provided";
            if (mMetadata.title == null) mMetadata.title = "Not Provided";
            if (mMetadata.artist == null) mMetadata.artist = "";
            if (mMetadata.album == null) mMetadata.album = "";
            if (mMetadata.trackNum == null) mMetadata.trackNum = "1";
            if (mMetadata.numTracks == null) mMetadata.numTracks = "1";
            if (mMetadata.genre == null) mMetadata.genre = "";
            if (mMetadata.duration == null) mMetadata.duration = "0";
            return this;
        }

        /**
         * Get the final Metadata objects you're building
         */
        public Metadata build() {
            return mMetadata.clone();
        }
    }
}
+41 −135
Original line number Diff line number Diff line
@@ -29,7 +29,7 @@ import java.util.ArrayList;
import java.util.List;

class Util {
    public static String TAG = "AvrcpUtil";
    public static String TAG = "audio_util.Util";
    public static boolean DEBUG = false;

    private static final String GPM_KEY = "com.google.android.music.mediasession.music_metadata";
@@ -37,6 +37,9 @@ class Util {
    // TODO (apanicke): Remove this prefix later, for now it makes debugging easier.
    public static final String NOW_PLAYING_PREFIX = "NowPlayingId";

    /**
     * Get an empty set of Metadata
     */
    public static final Metadata empty_data() {
        Metadata ret = new Metadata();
        ret.mediaId = "Not Provided";
@@ -50,160 +53,63 @@ class Util {
        return ret;
    }

    public static Metadata bundleToMetadata(Bundle bundle) {
        if (bundle == null) return empty_data();

        Metadata temp = new Metadata();
        temp.title = bundle.getString(MediaMetadata.METADATA_KEY_TITLE, "Not Provided");
        temp.artist = bundle.getString(MediaMetadata.METADATA_KEY_ARTIST, "");
        temp.album = bundle.getString(MediaMetadata.METADATA_KEY_ALBUM, "");
        temp.trackNum = "" + bundle.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER, 1);
        temp.numTracks = "" + bundle.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS, 1);
        temp.genre = bundle.getString(MediaMetadata.METADATA_KEY_GENRE, "");
        temp.duration = "" + bundle.getLong(MediaMetadata.METADATA_KEY_DURATION, 0);
        return temp;
    }

    public static Bundle descriptionToBundle(MediaDescription desc) {
        Bundle ret = new Bundle();
        if (desc == null) return ret;

        if (desc.getTitle() != null) {
            ret.putString(MediaMetadata.METADATA_KEY_TITLE, desc.getTitle().toString());
        }

        if (desc.getSubtitle() != null) {
            ret.putString(MediaMetadata.METADATA_KEY_ARTIST, desc.getSubtitle().toString());
        }

        if (desc.getDescription() != null) {
            ret.putString(MediaMetadata.METADATA_KEY_ALBUM, desc.getDescription().toString());
        }

        // If the bundle has title or artist use those over the description title or subtitle.
        if (desc.getExtras() != null) ret.putAll(desc.getExtras());

        if (ret.containsKey(GPM_KEY)) {
            if (DEBUG) Log.d(TAG, "MediaDescription contains GPM data");
            ret.putAll(mediaMetadataToBundle((MediaMetadata) ret.get(GPM_KEY)));
        }

        return ret;
    }

    public static Bundle mediaMetadataToBundle(MediaMetadata data) {
        Bundle bundle = new Bundle();
        if (data == null) return bundle;

        if (data.containsKey(MediaMetadata.METADATA_KEY_TITLE)) {
            bundle.putString(MediaMetadata.METADATA_KEY_TITLE,
                    data.getString(MediaMetadata.METADATA_KEY_TITLE));
        }

        if (data.containsKey(MediaMetadata.METADATA_KEY_ARTIST)) {
            bundle.putString(MediaMetadata.METADATA_KEY_ARTIST,
                    data.getString(MediaMetadata.METADATA_KEY_ARTIST));
        }

        if (data.containsKey(MediaMetadata.METADATA_KEY_ALBUM)) {
            bundle.putString(MediaMetadata.METADATA_KEY_ALBUM,
                    data.getString(MediaMetadata.METADATA_KEY_ALBUM));
        }

        if (data.containsKey(MediaMetadata.METADATA_KEY_TRACK_NUMBER)) {
            bundle.putLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER,
                    data.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER));
        }

        if (data.containsKey(MediaMetadata.METADATA_KEY_NUM_TRACKS)) {
            bundle.putLong(MediaMetadata.METADATA_KEY_NUM_TRACKS,
                    data.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS));
    /**
     * Translate a bundle of MediaMetadata keys to audio_util's Metadata
     */
    public static Metadata toMetadata(Bundle bundle) {
        Metadata.Builder builder = new Metadata.Builder();
        return builder.useDefaults().fromBundle(bundle).build();
    }

        if (data.containsKey(MediaMetadata.METADATA_KEY_GENRE)) {
            bundle.putString(MediaMetadata.METADATA_KEY_GENRE,
                    data.getString(MediaMetadata.METADATA_KEY_GENRE));
    /**
     * Translate a MediaDescription to audio_util's Metadata
     */
    public static Metadata toMetadata(MediaDescription desc) {
        // Find GPM_KEY data if it exists
        MediaMetadata data = null;
        Bundle extras = (desc != null ? desc.getExtras() : null);
        if (extras != null && extras.containsKey(GPM_KEY)) {
            data = (MediaMetadata) extras.get(GPM_KEY);
        }

        if (data.containsKey(MediaMetadata.METADATA_KEY_DURATION)) {
            bundle.putLong(MediaMetadata.METADATA_KEY_DURATION,
                    data.getLong(MediaMetadata.METADATA_KEY_DURATION));
        Metadata.Builder builder = new Metadata.Builder();
        return builder.useDefaults().fromMediaDescription(desc).fromMediaMetadata(data).build();
    }

        return bundle;
    /**
     * Translate a MediaItem to audio_util's Metadata
     */
    public static Metadata toMetadata(MediaItem item) {
        Metadata.Builder builder = new Metadata.Builder();
        return builder.useDefaults().fromMediaItem(item).build();
    }

    /**
     * Translate a MediaSession.QueueItem to audio_util's Metadata
     */
    public static Metadata toMetadata(MediaSession.QueueItem item) {
        if (item == null) {
            return empty_data();
        }

        Bundle bundle = descriptionToBundle(item.getDescription());

        if (DEBUG) {
            for (String key : bundle.keySet()) {
                Log.d(TAG, "toMetadata: QueueItem: ContainsKey: " + key);
            }
        }

        Metadata ret = bundleToMetadata(bundle);

        Metadata.Builder builder = new Metadata.Builder().useDefaults().fromQueueItem(item);
        // For Queue Items, the Media Id will always be just its Queue ID
        // We don't need to use its actual ID since we don't promise UIDS being valid
        // between a file system and it's now playing list.
        ret.mediaId = NOW_PLAYING_PREFIX + item.getQueueId();

        return ret;
        if (item != null) builder.setMediaId(NOW_PLAYING_PREFIX + item.getQueueId());
        return builder.build();
    }

    /**
     * Translate a MediaMetadata to audio_util's Metadata
     */
    public static Metadata toMetadata(MediaMetadata data) {
        if (data == null) {
            return empty_data();
        }

        MediaDescription desc = data.getDescription();

        Bundle dataBundle = mediaMetadataToBundle(data);
        Bundle bundle = descriptionToBundle(data.getDescription());

        // Prioritize the media metadata over the media description
        bundle.putAll(dataBundle);

        if (DEBUG) {
            for (String key : bundle.keySet()) {
                Log.d(TAG, "toMetadata: MediaMetadata: ContainsKey: " + key);
            }
        }

        Metadata ret = bundleToMetadata(bundle);

        Metadata.Builder builder = new Metadata.Builder();
        // This will always be currsong. The AVRCP service will overwrite the mediaId if it needs to
        // TODO (apanicke): Remove when the service is ready, right now it makes debugging much more
        // convenient
        ret.mediaId = "currsong";

        return ret;
    }

    public static Metadata toMetadata(MediaItem item) {
        if (item == null) {
            return empty_data();
        }

        Bundle bundle = descriptionToBundle(item.getDescription());

        if (DEBUG) {
            for (String key : bundle.keySet()) {
                Log.d(TAG, "toMetadata: MediaItem: ContainsKey: " + key);
            }
        }

        Metadata ret = bundleToMetadata(bundle);
        ret.mediaId = item.getMediaId();

        return ret;
        return builder.useDefaults().fromMediaMetadata(data).setMediaId("currsong").build();
    }

    /**
     * Translate a list of MediaSession.QueueItem to a list of audio_util's Metadata
     */
    public static List<Metadata> toMetadataList(List<MediaSession.QueueItem> items) {
        ArrayList<Metadata> list = new ArrayList<Metadata>();