Support API 23 methods in MBC and MBSC

- Use an extra messenger between MBSC (MediaBrowserServiceCompat)
  and MBC (MediaBrowserCompat) when the platform SDK version is
  21 or 22 in order to support the new method added in API 23.
- Use version numbers for better forward compatibility.

Bug: 22917960
Change-Id: Idf4247ee78ebfcf34434b92531618cc35151d038
diff --git a/v4/Android.mk b/v4/Android.mk
index d6fb925..87ea80e 100644
--- a/v4/Android.mk
+++ b/v4/Android.mk
@@ -201,7 +201,6 @@
 LOCAL_SDK_VERSION := 21
 LOCAL_SRC_FILES := $(call all-java-files-under, api21)
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4-api20
-LOCAL_JARJAR_RULES := $(LOCAL_PATH)/jarjar-rules.txt
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files += $(LOCAL_SRC_FILES)
@@ -240,6 +239,7 @@
 LOCAL_SRC_FILES := $(call all-java-files-under, java) \
     $(call all-Iaidl-files-under, java)
 LOCAL_STATIC_JAVA_LIBRARIES += android-support-v4-api23
+LOCAL_JARJAR_RULES := $(LOCAL_PATH)/jarjar-rules.txt
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 support_module_src_files += $(LOCAL_SRC_FILES)
diff --git a/v4/api21/android/support/v4/media/MediaBrowserCompatApi21.java b/v4/api21/android/support/v4/media/MediaBrowserCompatApi21.java
index c9ca69e..b285296 100644
--- a/v4/api21/android/support/v4/media/MediaBrowserCompatApi21.java
+++ b/v4/api21/android/support/v4/media/MediaBrowserCompatApi21.java
@@ -144,14 +144,4 @@
             mSubscriptionCallback.onError(parentId);
         }
     }
-
-    static class MediaItem {
-        public static Object getDescription(Object mediaItem) {
-            return ((MediaBrowser.MediaItem) mediaItem).getDescription();
-        }
-
-        public static int getFlags(Object mediaItem) {
-            return ((MediaBrowser.MediaItem) mediaItem).getFlags();
-        }
-    }
 }
diff --git a/v4/api21/android/support/v4/media/MediaBrowserServiceCompatApi21.java b/v4/api21/android/support/v4/media/MediaBrowserServiceCompatApi21.java
index b8363af..1baa14c 100644
--- a/v4/api21/android/support/v4/media/MediaBrowserServiceCompatApi21.java
+++ b/v4/api21/android/support/v4/media/MediaBrowserServiceCompatApi21.java
@@ -35,23 +35,22 @@
 class MediaBrowserServiceCompatApi21 {
 
     public static Object createService() {
-        return new MediaBrowserServiceAdaptor();
+        return new MediaBrowserServiceAdaptorApi21();
     }
 
-    public static void onCreate(Object serviceObj, ServiceImpl serviceImpl) {
-        ((MediaBrowserServiceAdaptor) serviceObj).onCreate(serviceImpl);
+    public static void onCreate(Object serviceObj, ServiceImplApi21 serviceImpl) {
+        ((MediaBrowserServiceAdaptorApi21) serviceObj).onCreate(serviceImpl);
     }
 
     public static IBinder onBind(Object serviceObj, Intent intent) {
-        return ((MediaBrowserServiceAdaptor) serviceObj).onBind(intent);
+        return ((MediaBrowserServiceAdaptorApi21) serviceObj).onBind(intent);
     }
 
-    public interface ServiceImpl {
+    public interface ServiceImplApi21 {
         void connect(final String pkg, final Bundle rootHints, final ServiceCallbacks callbacks);
         void disconnect(final ServiceCallbacks callbacks);
         void addSubscription(final String id, final ServiceCallbacks callbacks);
         void removeSubscription(final String id, final ServiceCallbacks callbacks);
-        void getMediaItem(final String mediaId, final ResultReceiver receiver);
     }
 
     public interface ServiceCallbacks {
@@ -95,11 +94,11 @@
         }
     }
 
-    private static class MediaBrowserServiceAdaptor {
-        ServiceBinderProxy mBinder;
+    static class MediaBrowserServiceAdaptorApi21 {
+        ServiceBinderProxyApi21 mBinder;
 
-        public void onCreate(ServiceImpl serviceImpl) {
-            mBinder = new ServiceBinderProxy(serviceImpl);
+        public void onCreate(ServiceImplApi21 serviceImpl) {
+            mBinder = new ServiceBinderProxyApi21(serviceImpl);
         }
 
         public IBinder onBind(Intent intent) {
@@ -109,10 +108,10 @@
             return null;
         }
 
-        private static class ServiceBinderProxy extends IMediaBrowserService.Stub {
-            private final ServiceImpl mServiceImpl;
+        static class ServiceBinderProxyApi21 extends IMediaBrowserService.Stub {
+            final ServiceImplApi21 mServiceImpl;
 
-            ServiceBinderProxy(ServiceImpl serviceImpl) {
+            ServiceBinderProxyApi21(ServiceImplApi21 serviceImpl) {
                 mServiceImpl = serviceImpl;
             }
 
@@ -142,7 +141,7 @@
 
             @Override
             public void getMediaItem(final String mediaId, final ResultReceiver receiver) {
-                mServiceImpl.getMediaItem(mediaId, receiver);
+                // No operation since this method is added in API 23.
             }
         }
     }
diff --git a/v4/api23/android/support/v4/media/MediaBrowserCompatApi23.java b/v4/api23/android/support/v4/media/MediaBrowserCompatApi23.java
new file mode 100644
index 0000000..1e9df1a
--- /dev/null
+++ b/v4/api23/android/support/v4/media/MediaBrowserCompatApi23.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2015 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 android.support.v4.media;
+
+import android.media.browse.MediaBrowser;
+import android.os.Parcel;
+import android.support.annotation.NonNull;
+
+class MediaBrowserCompatApi23 {
+
+    public static Object createItemCallback(ItemCallback callback) {
+        return new ItemCallbackProxy<>(callback);
+    }
+
+    public static void getItem(Object browserObj, String mediaId, Object itemCallbackObj) {
+        ((MediaBrowser) browserObj).getItem(mediaId, ((MediaBrowser.ItemCallback) itemCallbackObj));
+    }
+
+    interface ItemCallback {
+        void onItemLoaded(Parcel itemParcel);
+        void onError(@NonNull String itemId);
+    }
+
+    static class ItemCallbackProxy<T extends ItemCallback> extends MediaBrowser.ItemCallback {
+        protected final T mItemCallback;
+
+        public ItemCallbackProxy(T callback) {
+            mItemCallback = callback;
+        }
+
+        @Override
+        public void onItemLoaded(MediaBrowser.MediaItem item) {
+            Parcel parcel = Parcel.obtain();
+            item.writeToParcel(parcel, 0);
+            mItemCallback.onItemLoaded(parcel);
+        }
+
+        @Override
+        public void onError(@NonNull String itemId) {
+            mItemCallback.onError(itemId);
+        }
+    }
+}
diff --git a/v4/api23/android/support/v4/media/MediaBrowserServiceCompatApi23.java b/v4/api23/android/support/v4/media/MediaBrowserServiceCompatApi23.java
new file mode 100644
index 0000000..fcaea40
--- /dev/null
+++ b/v4/api23/android/support/v4/media/MediaBrowserServiceCompatApi23.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2015 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 android.support.v4.media;
+
+import android.media.browse.MediaBrowser;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.ResultReceiver;
+import android.service.media.MediaBrowserService;
+import android.util.Log;
+
+class MediaBrowserServiceCompatApi23 extends MediaBrowserServiceCompatApi21 {
+    private static final String TAG = "MediaBrowserServiceCompatApi21";
+
+    public static Object createService() {
+        return new MediaBrowserServiceAdaptorApi23();
+    }
+
+    public static void onCreate(Object serviceObj, ServiceImplApi23 serviceImpl) {
+        ((MediaBrowserServiceAdaptorApi23) serviceObj).onCreate(serviceImpl);
+    }
+
+    public interface ServiceImplApi23 extends ServiceImplApi21 {
+        void getMediaItem(final String mediaId, final ItemCallback cb);
+    }
+
+    public interface ItemCallback {
+        void onItemLoaded(int resultCode, Bundle resultData, Parcel itemParcel);
+    }
+
+    static class MediaBrowserServiceAdaptorApi23 extends MediaBrowserServiceAdaptorApi21 {
+
+        public void onCreate(ServiceImplApi23 serviceImpl) {
+            mBinder = new ServiceBinderProxyApi23(serviceImpl);
+        }
+
+        private static class ServiceBinderProxyApi23 extends ServiceBinderProxyApi21 {
+            ServiceImplApi23 mServiceImpl;
+
+            ServiceBinderProxyApi23(ServiceImplApi23 serviceImpl) {
+                super(serviceImpl);
+                mServiceImpl = serviceImpl;
+            }
+
+            @Override
+            public void getMediaItem(final String mediaId, final ResultReceiver receiver) {
+                final String KEY_MEDIA_ITEM;
+                try {
+                    KEY_MEDIA_ITEM = (String) MediaBrowserService.class.getDeclaredField(
+                            "KEY_MEDIA_ITEM").get(null);
+                } catch (IllegalAccessException | NoSuchFieldException e) {
+                    Log.i(TAG, "Failed to get KEY_MEDIA_ITEM via reflection", e);
+                    return;
+                }
+
+                mServiceImpl.getMediaItem(mediaId, new ItemCallback() {
+                    @Override
+                    public void onItemLoaded(int resultCode, Bundle resultData, Parcel itemParcel) {
+                        if (itemParcel != null) {
+                            itemParcel.setDataPosition(0);
+                            MediaBrowser.MediaItem item =
+                                    MediaBrowser.MediaItem.CREATOR.createFromParcel(itemParcel);
+                            resultData.putParcelable(KEY_MEDIA_ITEM, item);
+                            itemParcel.recycle();
+                        }
+                        receiver.send(resultCode, resultData);
+                    }
+                });
+            }
+        }
+    }
+}
diff --git a/v4/java/android/support/v4/media/MediaBrowserCompat.java b/v4/java/android/support/v4/media/MediaBrowserCompat.java
index 4884cf2..d46dc65 100644
--- a/v4/java/android/support/v4/media/MediaBrowserCompat.java
+++ b/v4/java/android/support/v4/media/MediaBrowserCompat.java
@@ -31,6 +31,7 @@
 import android.support.annotation.IntDef;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
+import android.support.v4.app.BundleCompat;
 import android.support.v4.media.session.MediaSessionCompat;
 import android.support.v4.os.ResultReceiver;
 import android.support.v4.util.ArrayMap;
@@ -43,6 +44,8 @@
 import java.util.Collections;
 import java.util.List;
 
+import static android.support.v4.media.MediaBrowserProtocol.*;
+
 /**
  * Browses media content offered by a {@link MediaBrowserServiceCompat}.
  * <p>
@@ -52,13 +55,7 @@
  * @hide
  */
 public final class MediaBrowserCompat {
-    public static final String DATA_RESULT_RECEIVER = "data_result_receiver";
-
-    // TODO: Consider introducing version numbers for MediaBrowserCompat and
-    // MediaBrowserServiceCompat.
-    public static final int MSG_ON_CONNECT = 1;
-    public static final int MSG_ON_CONNECT_FAILED = 2;
-    public static final int MSG_ON_LOAD_CHILDREN = 3;
+    private static final String TAG = "MediaBrowserCompat";
 
     private final MediaBrowserImpl mImpl;
 
@@ -75,8 +72,9 @@
      */
     public MediaBrowserCompat(Context context, ComponentName serviceComponent,
             ConnectionCallback callback, Bundle rootHints) {
-        // TODO: Implement MediaBrowserImplApi23.
-        if (android.os.Build.VERSION.SDK_INT >= 21) {
+        if (android.os.Build.VERSION.SDK_INT >= 23) {
+            mImpl = new MediaBrowserImplApi23(context, serviceComponent, callback, rootHints);
+        } else if (android.os.Build.VERSION.SDK_INT >= 21) {
             mImpl = new MediaBrowserImplApi21(context, serviceComponent, callback, rootHints);
         } else {
             mImpl = new MediaBrowserImplBase(context, serviceComponent, callback, rootHints);
@@ -442,6 +440,16 @@
      * Callback for receiving the result of {@link #getItem}.
      */
     public static abstract class ItemCallback {
+        final Object mItemCallbackObj;
+
+        public ItemCallback() {
+            if (android.os.Build.VERSION.SDK_INT >= 23) {
+                mItemCallbackObj = MediaBrowserCompatApi23.createItemCallback(new StubApi23());
+            } else {
+                mItemCallbackObj = null;
+            }
+        }
+
         /**
          * Called when the item has been returned by the browser service.
          *
@@ -457,6 +465,21 @@
          */
         public void onError(@NonNull String itemId) {
         }
+
+        private class StubApi23 implements MediaBrowserCompatApi23.ItemCallback {
+            @Override
+            public void onItemLoaded(Parcel itemParcel) {
+                itemParcel.setDataPosition(0);
+                MediaItem item = MediaBrowserCompat.MediaItem.CREATOR.createFromParcel(itemParcel);
+                itemParcel.recycle();
+                ItemCallback.this.onItemLoaded(item);
+            }
+
+            @Override
+            public void onError(@NonNull String itemId) {
+                ItemCallback.this.onError(itemId);
+            }
+        }
     }
 
     interface MediaBrowserImpl {
@@ -473,7 +496,6 @@
     }
 
     static class MediaBrowserImplBase implements MediaBrowserImpl {
-        private static final String TAG = "MediaBrowserCompat";
         private static final boolean DBG = false;
 
         private static final int CONNECT_STATE_DISCONNECTED = 0;
@@ -831,30 +853,30 @@
 
         private final void onLoadChildren(final Messenger callback, final String parentId,
                 final List list) {
-        // Check that there hasn't been a disconnect or a different ServiceConnection.
-        if (!isCurrent(callback, "onLoadChildren")) {
-            return;
-        }
-
-        List<MediaItem> data = list;
-        if (DBG) {
-            Log.d(TAG, "onLoadChildren for " + mServiceComponent + " id=" + parentId);
-        }
-        if (data == null) {
-            data = Collections.emptyList();
-        }
-
-        // Check that the subscription is still subscribed.
-        final Subscription subscription = mSubscriptions.get(parentId);
-        if (subscription == null) {
-            if (DBG) {
-                Log.d(TAG, "onLoadChildren for id that isn't subscribed id=" + parentId);
+            // Check that there hasn't been a disconnect or a different ServiceConnection.
+            if (!isCurrent(callback, "onLoadChildren")) {
+                return;
             }
-            return;
-        }
 
-        // Tell the app.
-        subscription.callback.onChildrenLoaded(parentId, data);
+            List<MediaItem> data = list;
+            if (DBG) {
+                Log.d(TAG, "onLoadChildren for " + mServiceComponent + " id=" + parentId);
+            }
+            if (data == null) {
+                data = Collections.emptyList();
+            }
+
+            // Check that the subscription is still subscribed.
+            final Subscription subscription = mSubscriptions.get(parentId);
+            if (subscription == null) {
+                if (DBG) {
+                    Log.d(TAG, "onLoadChildren for id that isn't subscribed id=" + parentId);
+                }
+                return;
+            }
+
+            // Tell the app.
+            subscription.callback.onChildrenLoaded(parentId, data);
         }
 
         /**
@@ -896,35 +918,33 @@
             }
 
             void connect() throws RemoteException {
-                sendRequest(MediaBrowserServiceCompat.MSG_CONNECT, mContext.getPackageName(),
-                        mRootHints, mCallbacksMessenger);
+                sendRequest(CLIENT_MSG_CONNECT, mContext.getPackageName(), mRootHints,
+                        mCallbacksMessenger);
             }
 
             void disconnect() throws RemoteException {
-                sendRequest(MediaBrowserServiceCompat.MSG_DISCONNECT, null, null,
-                        mCallbacksMessenger);
+                sendRequest(CLIENT_MSG_DISCONNECT, null, null, mCallbacksMessenger);
             }
 
             void addSubscription(String parentId) throws RemoteException {
-                sendRequest(MediaBrowserServiceCompat.MSG_ADD_SUBSCRIPTION, parentId, null,
-                        mCallbacksMessenger);
+                sendRequest(CLIENT_MSG_ADD_SUBSCRIPTION, parentId, null, mCallbacksMessenger);
             }
 
             void removeSubscription(String parentId) throws RemoteException {
-                sendRequest(MediaBrowserServiceCompat.MSG_REMOVE_SUBSCRIPTION, parentId, null,
-                        mCallbacksMessenger);
+                sendRequest(CLIENT_MSG_REMOVE_SUBSCRIPTION, parentId, null, mCallbacksMessenger);
             }
 
             void getMediaItem(String mediaId, ResultReceiver receiver) throws RemoteException {
                 Bundle data = new Bundle();
-                data.putParcelable(DATA_RESULT_RECEIVER, receiver);
-                sendRequest(MediaBrowserServiceCompat.MSG_GET_MEDIA_ITEM, mediaId, data, null);
+                data.putParcelable(SERVICE_DATA_RESULT_RECEIVER, receiver);
+                sendRequest(CLIENT_MSG_GET_MEDIA_ITEM, mediaId, data, null);
             }
 
             private void sendRequest(int what, Object obj, Bundle data, Messenger cbMessenger)
                     throws RemoteException {
                 Message msg = Message.obtain();
                 msg.what = what;
+                msg.arg1 = CLIENT_VERSION_CURRENT;
                 msg.obj = obj;
                 msg.setData(data);
                 msg.replyTo = cbMessenger;
@@ -1023,22 +1043,23 @@
             public void handleMessage(Message msg) {
                 Bundle data = msg.getData();
                 switch (msg.what) {
-                    case MSG_ON_CONNECT:
+                    case SERVICE_MSG_ON_CONNECT:
                         onServiceConnected(mCallbacksMessenger, (String) msg.obj,
                                 (MediaSessionCompat.Token) data.getParcelable(
-                                        MediaBrowserServiceCompat.DATA_MEDIA_SESSION_TOKEN),
-                                data.getBundle(MediaBrowserServiceCompat.DATA_EXTRAS));
+                                        SERVICE_DATA_MEDIA_SESSION_TOKEN),
+                                data.getBundle(SERVICE_DATA_EXTRAS));
                         break;
-                    case MSG_ON_CONNECT_FAILED:
+                    case SERVICE_MSG_ON_CONNECT_FAILED:
                         onConnectionFailed(mCallbacksMessenger);
                         break;
-                    case MSG_ON_LOAD_CHILDREN:
+                    case SERVICE_MSG_ON_LOAD_CHILDREN:
                         onLoadChildren(mCallbacksMessenger,  (String) msg.obj,
-                                data.getParcelableArrayList(
-                                        MediaBrowserServiceCompat.DATA_MEDIA_ITEM_LIST));
+                                data.getParcelableArrayList(SERVICE_DATA_MEDIA_ITEM_LIST));
                         break;
                     default:
-                        super.handleMessage(msg);
+                        Log.w(TAG, "Unhandled message: " + msg
+                                + "\n  Client version: " + CLIENT_VERSION_CURRENT
+                                + "\n  Service version: " + msg.arg1);
                 }
             }
         }
@@ -1054,7 +1075,9 @@
     }
 
     static class MediaBrowserImplApi21 implements MediaBrowserImpl {
-        Object mBrowserObj;
+        protected Object mBrowserObj;
+        protected Messenger mMessenger;
+        protected Handler mHandler = new Handler();
 
         public MediaBrowserImplApi21(Context context, ComponentName serviceComponent,
                 ConnectionCallback callback, Bundle rootHints) {
@@ -1113,9 +1136,93 @@
         }
 
         @Override
+        public void getItem(@NonNull final String mediaId, @NonNull final ItemCallback cb) {
+            if (TextUtils.isEmpty(mediaId)) {
+                throw new IllegalArgumentException("mediaId is empty.");
+            }
+            if (cb == null) {
+                throw new IllegalArgumentException("cb is null.");
+            }
+            if (!MediaBrowserCompatApi21.isConnected(mBrowserObj)) {
+                Log.i(TAG, "Not connected, unable to retrieve the MediaItem.");
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        cb.onError(mediaId);
+                    }
+                });
+                return;
+            }
+            if (mMessenger == null) {
+                Bundle extras = MediaBrowserCompatApi21.getExtras(mBrowserObj);
+                IBinder serviceBinder = BundleCompat.getBinder(extras, EXTRA_MESSENGER_BINDER);
+                if (serviceBinder != null) {
+                    mMessenger = new Messenger(serviceBinder);
+                }
+            }
+            if (mMessenger == null) {
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        // Default framework implementation.
+                        cb.onItemLoaded(null);
+                    }
+                });
+                return;
+            }
+            ResultReceiver receiver = new ResultReceiver(mHandler) {
+                @Override
+                protected void onReceiveResult(int resultCode, Bundle resultData) {
+                    if (resultCode != 0 || resultData == null
+                            || !resultData.containsKey(MediaBrowserServiceCompat.KEY_MEDIA_ITEM)) {
+                        cb.onError(mediaId);
+                        return;
+                    }
+                    Parcelable item =
+                            resultData.getParcelable(MediaBrowserServiceCompat.KEY_MEDIA_ITEM);
+                    if (!(item instanceof MediaItem)) {
+                        cb.onError(mediaId);
+                        return;
+                    }
+                    cb.onItemLoaded((MediaItem)item);
+                }
+            };
+            try {
+                Bundle data = new Bundle();
+                data.putParcelable(SERVICE_DATA_RESULT_RECEIVER, receiver);
+                sendRequest(CLIENT_MSG_GET_MEDIA_ITEM, mediaId, data, null);
+            } catch (RemoteException e) {
+                Log.i(TAG, "Remote error getting media item.");
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        cb.onError(mediaId);
+                    }
+                });
+            }
+        }
+
+        private void sendRequest(int what, Object obj, Bundle data, Messenger cbMessenger)
+                throws RemoteException {
+            Message msg = Message.obtain();
+            msg.what = what;
+            msg.arg1 = CLIENT_VERSION_CURRENT;
+            msg.obj = obj;
+            msg.setData(data);
+            msg.replyTo = cbMessenger;
+            mMessenger.send(msg);
+        }
+    }
+
+    static class MediaBrowserImplApi23 extends MediaBrowserImplApi21 {
+        public MediaBrowserImplApi23(Context context, ComponentName serviceComponent,
+                ConnectionCallback callback, Bundle rootHints) {
+            super(context, serviceComponent, callback, rootHints);
+        }
+
+        @Override
         public void getItem(@NonNull String mediaId, @NonNull ItemCallback cb) {
-            // TODO Implement individual item loading on API 21-22 devices
-            cb.onItemLoaded(null);
+            MediaBrowserCompatApi23.getItem(mBrowserObj, mediaId, cb.mItemCallbackObj);
         }
     }
 }
diff --git a/v4/java/android/support/v4/media/MediaBrowserProtocol.java b/v4/java/android/support/v4/media/MediaBrowserProtocol.java
new file mode 100644
index 0000000..7c8feb3
--- /dev/null
+++ b/v4/java/android/support/v4/media/MediaBrowserProtocol.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2015 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 android.support.v4.media;
+
+/***
+ * Defines the communication protocol for media browsers and media browser services.
+ * @hide
+ */
+class MediaBrowserProtocol {
+
+    /**
+     * 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;
+    public static final int SERVICE_VERSION_CURRENT = SERVICE_VERSION_1;
+
+    /*
+     * 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
+     * - obj  : The root media item id
+     * - data
+     *     SERVICE_DATA_MEDIA_SESSION_TOKEN : Media session token
+     *     SERVICE_DATA_EXTRAS : An extras bundle which contains EXTRA_SERVICE_VERSION
+     */
+    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
+     * - obj  : The parent media item id
+     * - data
+     *     SERVICE_DATA_MEDIA_ITEM_LIST : An array list for the media item children
+     */
+    public static final int SERVICE_MSG_ON_LOAD_CHILDREN = 3;
+
+    public static final String SERVICE_DATA_MEDIA_SESSION_TOKEN = "data_media_session_token";
+    public static final String SERVICE_DATA_EXTRAS = "data_extras";
+    public static final String SERVICE_DATA_MEDIA_ITEM_LIST = "data_media_item_list";
+    public static final String SERVICE_DATA_RESULT_RECEIVER = "data_result_receiver";
+
+    public static final String EXTRA_SERVICE_VERSION = "extra_service_version";
+    public static final String EXTRA_MESSENGER_BINDER = "extra_messenger";
+
+    /**
+     * 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
+     * - obj  : The package name
+     * - data : An optional root hints bundle of service-specific arguments
+     * - replayTo : Client messenger
+     */
+    public static final int CLIENT_MSG_CONNECT = 1;
+
+    /** (client v1)
+     * Sent to disconnect from the media browse service compat.
+     * - arg1 : The client version
+     * - replayTo : Client 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
+     * - obj  : The media item id
+     * - replayTo : Client 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
+     * - obj  : The media item id
+     * - replayTo : Client messenger
+     */
+    public static final int CLIENT_MSG_REMOVE_SUBSCRIPTION = 4;
+
+    /** (client v1)
+     * Sent to retrieves a specific media item from the connected service.
+     * - arg1 : The client version
+     * - obj  : The media item id
+     * - data
+     *     SERVICE_DATA_RESULT_RECEIVER : Result receiver to get the result
+     */
+    public static final int CLIENT_MSG_GET_MEDIA_ITEM = 5;
+}
diff --git a/v4/java/android/support/v4/media/MediaBrowserServiceCompat.java b/v4/java/android/support/v4/media/MediaBrowserServiceCompat.java
index ded7e7e..c618e4b 100644
--- a/v4/java/android/support/v4/media/MediaBrowserServiceCompat.java
+++ b/v4/java/android/support/v4/media/MediaBrowserServiceCompat.java
@@ -30,6 +30,7 @@
 import android.os.RemoteException;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
+import android.support.v4.app.BundleCompat;
 import android.support.v4.media.session.MediaSessionCompat;
 import android.support.v4.os.ResultReceiver;
 import android.support.v4.util.ArrayMap;
@@ -42,6 +43,8 @@
 import java.util.HashSet;
 import java.util.List;
 
+import static android.support.v4.media.MediaBrowserProtocol.*;
+
 /**
  * Base class for media browse services.
  * <p>
@@ -68,16 +71,6 @@
     private static final String TAG = "MediaBrowserServiceCompat";
     private static final boolean DBG = false;
 
-    public static final String DATA_MEDIA_SESSION_TOKEN = "data_media_session_token";
-    public static final String DATA_EXTRAS = "data_extras";
-    public static final String DATA_MEDIA_ITEM_LIST = "data_media_item_list";
-
-    public static final int MSG_CONNECT = 1;
-    public static final int MSG_DISCONNECT = 2;
-    public static final int MSG_ADD_SUBSCRIPTION = 3;
-    public static final int MSG_REMOVE_SUBSCRIPTION = 4;
-    public static final int MSG_GET_MEDIA_ITEM = 5;
-
     private MediaBrowserServiceImpl mImpl;
 
     /**
@@ -124,8 +117,7 @@
         @Override
         public void onCreate() {
             mServiceObj = MediaBrowserServiceCompatApi21.createService();
-            MediaBrowserServiceCompatApi21.onCreate(mServiceObj,
-                    new ServiceImplApi21(mHandler.getServiceImpl()));
+            MediaBrowserServiceCompatApi21.onCreate(mServiceObj, new ServiceImplApi21());
         }
 
         @Override
@@ -134,33 +126,50 @@
         }
     }
 
+    class MediaBrowserServiceImplApi23 implements MediaBrowserServiceImpl {
+        private Object mServiceObj;
+
+        @Override
+        public void onCreate() {
+            mServiceObj = MediaBrowserServiceCompatApi23.createService();
+            MediaBrowserServiceCompatApi23.onCreate(mServiceObj, new ServiceImplApi23());
+        }
+
+        @Override
+        public IBinder onBind(Intent intent) {
+            return MediaBrowserServiceCompatApi23.onBind(mServiceObj, intent);
+        }
+    }
+
     private final class ServiceHandler extends Handler {
         private final ServiceImpl mServiceImpl = new ServiceImpl();
 
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
-                case MSG_CONNECT:
+                case CLIENT_MSG_CONNECT:
                     mServiceImpl.connect((String) msg.obj, msg.getData(),
                             new ServiceCallbacksCompat(msg.replyTo));
                     break;
-                case MSG_DISCONNECT:
+                case CLIENT_MSG_DISCONNECT:
                     mServiceImpl.disconnect(new ServiceCallbacksCompat(msg.replyTo));
                     break;
-                case MSG_ADD_SUBSCRIPTION:
+                case CLIENT_MSG_ADD_SUBSCRIPTION:
                     mServiceImpl.addSubscription((String) msg.obj,
                             new ServiceCallbacksCompat(msg.replyTo));
                     break;
-                case MSG_REMOVE_SUBSCRIPTION:
+                case CLIENT_MSG_REMOVE_SUBSCRIPTION:
                     mServiceImpl.removeSubscription((String) msg.obj,
                             new ServiceCallbacksCompat(msg.replyTo));
                     break;
-                case MSG_GET_MEDIA_ITEM:
+                case CLIENT_MSG_GET_MEDIA_ITEM:
                     mServiceImpl.getMediaItem((String) msg.obj, (ResultReceiver) msg.getData()
-                            .getParcelable(MediaBrowserCompat.DATA_RESULT_RECEIVER));
+                            .getParcelable(SERVICE_DATA_RESULT_RECEIVER));
                     break;
                 default:
-                    super.handleMessage(msg);
+                    Log.w(TAG, "Unhandled message: " + msg
+                            + "\n  Service version: " + SERVICE_VERSION_CURRENT
+                            + "\n  Client version: " + msg.arg1);
             }
         }
 
@@ -371,11 +380,11 @@
         }
     }
 
-    private class ServiceImplApi21 implements MediaBrowserServiceCompatApi21.ServiceImpl {
+    private class ServiceImplApi21 implements MediaBrowserServiceCompatApi21.ServiceImplApi21 {
         final ServiceImpl mServiceImpl;
 
-        ServiceImplApi21(ServiceImpl serviceImpl) {
-            mServiceImpl = serviceImpl;
+        ServiceImplApi21() {
+            mServiceImpl = mHandler.getServiceImpl();
         }
 
         @Override
@@ -401,13 +410,23 @@
                 final MediaBrowserServiceCompatApi21.ServiceCallbacks callbacks) {
             mServiceImpl.removeSubscription(id, new ServiceCallbacksApi21(callbacks));
         }
+    }
 
+    private class ServiceImplApi23 extends ServiceImplApi21
+            implements MediaBrowserServiceCompatApi23.ServiceImplApi23 {
         @Override
-        public void getMediaItem(final String mediaId, final android.os.ResultReceiver receiver) {
+        public void getMediaItem(final String mediaId,
+                final MediaBrowserServiceCompatApi23.ItemCallback cb) {
             ResultReceiver receiverCompat = new ResultReceiver(mHandler) {
                 @Override
                 protected void onReceiveResult(int resultCode, Bundle resultData) {
-                    receiver.send(resultCode, resultData);
+                    MediaBrowserCompat.MediaItem item = resultData.getParcelable(KEY_MEDIA_ITEM);
+                    Parcel itemParcel = null;
+                    if (item != null) {
+                        itemParcel = Parcel.obtain();
+                        item.writeToParcel(itemParcel, 0);
+                    }
+                    cb.onItemLoaded(resultCode, resultData, itemParcel);
                 }
             };
             mServiceImpl.getMediaItem(mediaId, receiverCompat);
@@ -436,27 +455,33 @@
 
         public void onConnect(String root, MediaSessionCompat.Token session, Bundle extras)
                 throws RemoteException {
+            if (extras == null) {
+                extras = new Bundle();
+            }
+            extras.putInt(EXTRA_SERVICE_VERSION, SERVICE_VERSION_CURRENT);
             Bundle data = new Bundle();
-            data.putParcelable(DATA_MEDIA_SESSION_TOKEN, session);
-            data.putBundle(DATA_EXTRAS, extras);
-            sendRequest(MediaBrowserCompat.MSG_ON_CONNECT, root, data);
+            data.putParcelable(SERVICE_DATA_MEDIA_SESSION_TOKEN, session);
+            data.putBundle(SERVICE_DATA_EXTRAS, extras);
+            sendRequest(SERVICE_MSG_ON_CONNECT, root, data);
         }
 
         public void onConnectFailed() throws RemoteException {
-            sendRequest(MediaBrowserCompat.MSG_ON_CONNECT_FAILED, null, null);
+            sendRequest(SERVICE_MSG_ON_CONNECT_FAILED, null, null);
         }
 
         public void onLoadChildren(String mediaId, List<MediaBrowserCompat.MediaItem> list)
                 throws RemoteException {
             Bundle data = new Bundle();
-            data.putParcelableArrayList(DATA_MEDIA_ITEM_LIST,
+            data.putParcelableArrayList(SERVICE_DATA_MEDIA_ITEM_LIST,
                     list instanceof ArrayList ? (ArrayList) list : new ArrayList<>(list));
-            sendRequest(MediaBrowserCompat.MSG_ON_LOAD_CHILDREN, mediaId, data);
+            sendRequest(SERVICE_MSG_ON_LOAD_CHILDREN, mediaId, data);
         }
 
-        private void sendRequest(int what, Object obj, Bundle data) throws RemoteException {
+        private void sendRequest(int what, Object obj, Bundle data)
+                throws RemoteException {
             Message msg = Message.obtain();
             msg.what = what;
+            msg.arg1 = SERVICE_VERSION_CURRENT;
             msg.obj = obj;
             msg.setData(data);
             mCallbacks.send(msg);
@@ -465,6 +490,7 @@
 
     private class ServiceCallbacksApi21 implements ServiceCallbacks {
         final MediaBrowserServiceCompatApi21.ServiceCallbacks mCallbacks;
+        Messenger mMessenger;
 
         ServiceCallbacksApi21(MediaBrowserServiceCompatApi21.ServiceCallbacks callbacks) {
             mCallbacks = callbacks;
@@ -476,6 +502,12 @@
 
         public void onConnect(String root, MediaSessionCompat.Token session, Bundle extras)
                 throws RemoteException {
+            if (extras == null) {
+                extras = new Bundle();
+            }
+            mMessenger = new Messenger(mHandler);
+            BundleCompat.putBinder(extras, EXTRA_MESSENGER_BINDER, mMessenger.getBinder());
+            extras.putInt(EXTRA_SERVICE_VERSION, SERVICE_VERSION_CURRENT);
             mCallbacks.onConnect(root, session.getToken(), extras);
         }
 
@@ -501,7 +533,9 @@
     @Override
     public void onCreate() {
         super.onCreate();
-        if (Build.VERSION.SDK_INT >= 21) {
+        if (Build.VERSION.SDK_INT >= 23) {
+            mImpl = new MediaBrowserServiceImplApi23();
+        } else if (Build.VERSION.SDK_INT >= 21) {
             mImpl = new MediaBrowserServiceImplApi21();
         } else {
             mImpl = new MediaBrowserServiceImplBase();