MediaRouter: Black magic to bring custom media metadata back

MediaMetadataCompat and MediaMetadata are internally converted into each
other on platform version 21 or higher as the framework
(e.g. MediaSession) does not understand MediaMetadataCompat. When this
happens, custom key-value pairs are lost.

The difficulty here is that the current framework API does not provide a
way to tell the type of each custom key-value pair in MediaMetadata
making it impossible to extract them while converting.
This could've been avoided if there is a way to retrieve data as a
generic object such as Object or Bundle but the current design offers
get-methods only for specific types.

The code change here tries to overcome this problem by copying the
underlying Bundle object that represents the whole media metadata as is
when performing the conversion hence preserving the entirety of the
data. Care was taken to replace RatingCompat values in metadata with
Rating values such that the metadata Bundle can be unmarshalled on the
framework side.

Bug: 25328581
Change-Id: Ic83b73e18e31baf9446ca845b7e49aa508b3f108
diff --git a/v4/api21/android/support/v4/media/MediaMetadataCompatApi21.java b/v4/api21/android/support/v4/media/MediaMetadataCompatApi21.java
index eddcf76..fd51f78 100644
--- a/v4/api21/android/support/v4/media/MediaMetadataCompatApi21.java
+++ b/v4/api21/android/support/v4/media/MediaMetadataCompatApi21.java
@@ -19,6 +19,7 @@
 import android.graphics.Bitmap;
 import android.media.MediaMetadata;
 import android.media.Rating;
+import android.os.Parcel;
 
 import java.util.Set;
 
@@ -43,6 +44,14 @@
         return ((MediaMetadata) metadataObj).getText(key);
     }
 
+    public static void writeToParcel(Object metadataObj, Parcel dest, int flags) {
+        ((MediaMetadata) metadataObj).writeToParcel(dest, flags);
+    }
+
+    public static Object createFromParcel(Parcel in) {
+        return MediaMetadata.CREATOR.createFromParcel(in);
+    }
+
     public static class Builder {
         public static Object newInstance() {
             return new MediaMetadata.Builder();
diff --git a/v4/java/android/support/v4/media/MediaMetadataCompat.java b/v4/java/android/support/v4/media/MediaMetadataCompat.java
index d6ebfa4..0d07826 100644
--- a/v4/java/android/support/v4/media/MediaMetadataCompat.java
+++ b/v4/java/android/support/v4/media/MediaMetadataCompat.java
@@ -361,7 +361,13 @@
     public RatingCompat getRating(@RatingKey String key) {
         RatingCompat rating = null;
         try {
-            rating = mBundle.getParcelable(key);
+            if (Build.VERSION.SDK_INT >= 21) {
+                // On platform version 21 or higher, mBundle stores a Rating object. Convert it to
+                // RatingCompat.
+                rating = RatingCompat.fromRating(mBundle.getParcelable(key));
+            } else {
+                rating = mBundle.getParcelable(key);
+            }
         } catch (Exception e) {
             // ignore, value was not a bitmap
             Log.w(TAG, "Failed to retrieve a key as Rating.", e);
@@ -510,31 +516,11 @@
             return null;
         }
 
-        Builder builder = new Builder();
-        for (String key : MediaMetadataCompatApi21.keySet(metadataObj)) {
-            Integer type = METADATA_KEYS_TYPE.get(key);
-            if (type != null) {
-                switch (type) {
-                    case METADATA_TYPE_BITMAP:
-                        builder.putBitmap(key,
-                                MediaMetadataCompatApi21.getBitmap(metadataObj, key));
-                        break;
-                    case METADATA_TYPE_LONG:
-                        builder.putLong(key,
-                                MediaMetadataCompatApi21.getLong(metadataObj, key));
-                        break;
-                    case METADATA_TYPE_RATING:
-                        builder.putRating(key, RatingCompat.fromRating(
-                                MediaMetadataCompatApi21.getRating(metadataObj, key)));
-                        break;
-                    case METADATA_TYPE_TEXT:
-                        builder.putText(key,
-                                MediaMetadataCompatApi21.getText(metadataObj, key));
-                        break;
-                }
-            }
-        }
-        MediaMetadataCompat metadata = builder.build();
+        Parcel p = Parcel.obtain();
+        MediaMetadataCompatApi21.writeToParcel(metadataObj, p, 0);
+        p.setDataPosition(0);
+        MediaMetadataCompat metadata = MediaMetadataCompat.CREATOR.createFromParcel(p);
+        p.recycle();
         metadata.mMetadataObj = metadataObj;
         return metadata;
     }
@@ -554,31 +540,11 @@
             return mMetadataObj;
         }
 
-        Object builderObj = MediaMetadataCompatApi21.Builder.newInstance();
-        for (String key : keySet()) {
-            Integer type = METADATA_KEYS_TYPE.get(key);
-            if (type != null) {
-                switch (type) {
-                    case METADATA_TYPE_BITMAP:
-                        MediaMetadataCompatApi21.Builder.putBitmap(builderObj, key,
-                                getBitmap(key));
-                        break;
-                    case METADATA_TYPE_LONG:
-                        MediaMetadataCompatApi21.Builder.putLong(builderObj, key,
-                                getLong(key));
-                        break;
-                    case METADATA_TYPE_RATING:
-                        MediaMetadataCompatApi21.Builder.putRating(builderObj, key,
-                                getRating(key).getRating());
-                        break;
-                    case METADATA_TYPE_TEXT:
-                        MediaMetadataCompatApi21.Builder.putText(builderObj, key,
-                                getText(key));
-                        break;
-                }
-            }
-        }
-        mMetadataObj = MediaMetadataCompatApi21.Builder.build(builderObj);
+        Parcel p = Parcel.obtain();
+        writeToParcel(p, 0);
+        p.setDataPosition(0);
+        mMetadataObj = MediaMetadataCompatApi21.createFromParcel(p);
+        p.recycle();
         return mMetadataObj;
     }
 
@@ -742,7 +708,13 @@
                             + " key cannot be used to put a Rating");
                 }
             }
-            mBundle.putParcelable(key, value);
+            if (Build.VERSION.SDK_INT >= 21) {
+                // On platform version 21 or higher, use Rating instead of RatingCompat so mBundle
+                // can be unmarshalled.
+                mBundle.putParcelable(key, (Parcelable) value.getRating());
+            } else {
+                mBundle.putParcelable(key, value);
+            }
             return this;
         }