MediaBrowserCompat: Refactor class hierarchies

Change-Id: Iac6c823a6eeadb81a8af8c09a2103730a61e933c
diff --git a/v4/api21/android/support/v4/media/IMediaBrowserServiceAdapterApi21.java b/v4/api21/android/support/v4/media/IMediaBrowserServiceAdapterApi21.java
deleted file mode 100644
index 8db5fcb..0000000
--- a/v4/api21/android/support/v4/media/IMediaBrowserServiceAdapterApi21.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2016 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.os.Binder;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.IInterface;
-import android.os.Parcel;
-import android.os.RemoteException;
-import android.os.ResultReceiver;
-
-/**
- * An adapter class for replacing the auto generated hidden class, IMediaBrowserService.Stub
- */
-class IMediaBrowserServiceAdapterApi21 {
-    static abstract class Stub extends Binder implements IInterface {
-        private static final String DESCRIPTOR = "android.service.media.IMediaBrowserService";
-        // Following TRANSACTION_XXX values are synchronized with the auto generated java file
-        // from IMediaBrowserService.aidl
-        private static final int TRANSACTION_connect = IBinder.FIRST_CALL_TRANSACTION + 0;
-        private static final int TRANSACTION_disconnect = IBinder.FIRST_CALL_TRANSACTION + 1;
-        private static final int TRANSACTION_addSubscription = IBinder.FIRST_CALL_TRANSACTION + 2;
-        private static final int TRANSACTION_removeSubscription =
-                IBinder.FIRST_CALL_TRANSACTION + 3;
-        private static final int TRANSACTION_getMediaItem = IBinder.FIRST_CALL_TRANSACTION + 4;
-
-        public Stub() {
-            attachInterface(this, DESCRIPTOR);
-        }
-
-        @Override
-        public IBinder asBinder() {
-            return this;
-        }
-
-        @Override
-        public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
-                throws RemoteException {
-            switch (code) {
-                case IBinder.INTERFACE_TRANSACTION: {
-                    reply.writeString(DESCRIPTOR);
-                    return true;
-                }
-                case TRANSACTION_connect: {
-                    data.enforceInterface(DESCRIPTOR);
-                    String arg0 = data.readString();
-                    Bundle arg1;
-                    if (data.readInt() != 0) {
-                        arg1 = Bundle.CREATOR.createFromParcel(data);
-                    } else {
-                        arg1 = null;
-                    }
-                    Object arg2 = IMediaBrowserServiceCallbacksAdapterApi21.Stub.asInterface(
-                            data.readStrongBinder());
-                    connect(arg0, arg1, arg2);
-                    return true;
-                }
-                case TRANSACTION_disconnect: {
-                    data.enforceInterface(DESCRIPTOR);
-                    Object arg0 = IMediaBrowserServiceCallbacksAdapterApi21.Stub.asInterface(
-                            data.readStrongBinder());
-                    disconnect(arg0);
-                    return true;
-                }
-                case TRANSACTION_addSubscription: {
-                    data.enforceInterface(DESCRIPTOR);
-                    String arg0 = data.readString();
-                    Object arg1 = IMediaBrowserServiceCallbacksAdapterApi21.Stub.asInterface(
-                            data.readStrongBinder());
-                    addSubscription(arg0, arg1);
-                    return true;
-                }
-                case TRANSACTION_removeSubscription: {
-                    data.enforceInterface(DESCRIPTOR);
-                    String arg0 = data.readString();
-                    Object arg1 = IMediaBrowserServiceCallbacksAdapterApi21.Stub.asInterface(
-                            data.readStrongBinder());
-                    removeSubscription(arg0, arg1);
-                    return true;
-                }
-                case TRANSACTION_getMediaItem: {
-                    data.enforceInterface(DESCRIPTOR);
-                    String arg0 = data.readString();
-                    ResultReceiver arg1;
-                    if (data.readInt() != 0) {
-                        arg1 = android.os.ResultReceiver.CREATOR.createFromParcel(data);
-                    } else {
-                        arg1 = null;
-                    }
-                    getMediaItem(arg0, arg1);
-                    return true;
-                }
-            }
-            return super.onTransact(code, data, reply, flags);
-        }
-
-        public abstract void connect(final String pkg, final Bundle rootHints,
-                final Object callbacks);
-        public abstract void disconnect(final Object callbacks);
-        public abstract void addSubscription(final String id, final Object callbacks);
-        public abstract void removeSubscription(final String id, final Object callbacks);
-        public abstract void getMediaItem(final String mediaId, final ResultReceiver receiver);
-    }
-}
diff --git a/v4/api21/android/support/v4/media/MediaBrowserCompatApi21.java b/v4/api21/android/support/v4/media/MediaBrowserCompatApi21.java
index c6ccb14..3dc4e7c 100644
--- a/v4/api21/android/support/v4/media/MediaBrowserCompatApi21.java
+++ b/v4/api21/android/support/v4/media/MediaBrowserCompatApi21.java
@@ -129,25 +129,26 @@
         @Override
         public void onChildrenLoaded(@NonNull String parentId,
                 List<MediaBrowser.MediaItem> children) {
-            List<Parcel> parcelList = null;
-            if (children != null && children.size() == 1
-                    && children.get(0).getMediaId().equals(NULL_MEDIA_ITEM_ID)) {
-                children = null;
-            }
-            if (children != null) {
-                parcelList = new ArrayList<>();
-                for (MediaBrowser.MediaItem item : children) {
-                    Parcel parcel = Parcel.obtain();
-                    item.writeToParcel(parcel, 0);
-                    parcelList.add(parcel);
-                }
-            }
-            mSubscriptionCallback.onChildrenLoaded(parentId, parcelList);
+            mSubscriptionCallback.onChildrenLoaded(parentId, itemListToParcelList(children));
         }
 
         @Override
         public void onError(@NonNull String parentId) {
             mSubscriptionCallback.onError(parentId);
         }
+
+        static List<Parcel> itemListToParcelList(List<MediaBrowser.MediaItem> itemList) {
+            if (itemList == null || (itemList.size() == 1
+                    && itemList.get(0).getMediaId().equals(NULL_MEDIA_ITEM_ID))) {
+                return null;
+            }
+            List<Parcel> parcelList = new ArrayList<>();
+            for (MediaBrowser.MediaItem item : itemList) {
+                Parcel parcel = Parcel.obtain();
+                item.writeToParcel(parcel, 0);
+                parcelList.add(parcel);
+            }
+            return parcelList;
+        }
     }
 }
diff --git a/v4/api21/android/support/v4/media/MediaBrowserServiceCompatApi21.java b/v4/api21/android/support/v4/media/MediaBrowserServiceCompatApi21.java
index a09a74b..1538350 100644
--- a/v4/api21/android/support/v4/media/MediaBrowserServiceCompatApi21.java
+++ b/v4/api21/android/support/v4/media/MediaBrowserServiceCompatApi21.java
@@ -19,18 +19,27 @@
 import android.content.Intent;
 import android.media.MediaDescription;
 import android.media.browse.MediaBrowser;
+import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.RemoteException;
-import android.os.ResultReceiver;
 import android.service.media.MediaBrowserService;
 
 import java.util.ArrayList;
 import java.util.List;
 
 class MediaBrowserServiceCompatApi21 {
+    private static Object sNullParceledListSliceObj;
+    static {
+        MediaDescription nullDescription = new MediaDescription.Builder().setMediaId(
+                MediaBrowserCompatApi21.NULL_MEDIA_ITEM_ID).build();
+        MediaBrowser.MediaItem nullMediaItem = new MediaBrowser.MediaItem(nullDescription, 0);
+        List<MediaBrowser.MediaItem> nullMediaItemList = new ArrayList<>();
+        nullMediaItemList.add(nullMediaItem);
+        sNullParceledListSliceObj = ParceledListSliceAdapterApi21.newInstance(nullMediaItemList);
+    }
 
     public static Object createService() {
         return new MediaBrowserServiceAdaptorApi21();
@@ -44,35 +53,41 @@
         return ((MediaBrowserServiceAdaptorApi21) serviceObj).onBind(intent);
     }
 
-    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);
+    public static Object parcelListToParceledListSliceObject(List<Parcel> list) {
+        if (list == null) {
+            if (Build.VERSION.SDK_INT <= 23) {
+                return sNullParceledListSliceObj;
+            }
+            return null;
+        }
+        List<MediaBrowser.MediaItem> itemList = new ArrayList<>();
+        for (Parcel parcel : list) {
+            parcel.setDataPosition(0);
+            itemList.add(MediaBrowser.MediaItem.CREATOR.createFromParcel(parcel));
+            parcel.recycle();
+        }
+        return ParceledListSliceAdapterApi21.newInstance(itemList);
     }
 
-    public interface ServiceCallbacks {
+    public interface ServiceImplApi21 {
+        void connect(String pkg, Bundle rootHints, ServiceCallbacksApi21 callbacks);
+        void disconnect(ServiceCallbacksApi21 callbacks);
+        void addSubscription(String id, ServiceCallbacksApi21 callbacks);
+        void removeSubscription(String id, ServiceCallbacksApi21 callbacks);
+    }
+
+    public interface ServiceCallbacksApi21 {
         IBinder asBinder();
         void onConnect(String root, Object session, Bundle extras) throws RemoteException;
         void onConnectFailed() throws RemoteException;
         void onLoadChildren(String mediaId, List<Parcel> list) throws RemoteException;
     }
 
-    public static class ServiceCallbacksApi21 implements ServiceCallbacks {
-        private static Object sNullParceledListSliceObj;
-        static {
-            MediaDescription nullDescription = new MediaDescription.Builder().setMediaId(
-                    MediaBrowserCompatApi21.NULL_MEDIA_ITEM_ID).build();
-            MediaBrowser.MediaItem nullMediaItem = new MediaBrowser.MediaItem(nullDescription, 0);
-            List<MediaBrowser.MediaItem> nullMediaItemList = new ArrayList<>();
-            nullMediaItemList.add(nullMediaItem);
-            sNullParceledListSliceObj = ParceledListSliceAdapterApi21.newInstance(nullMediaItemList);
-        }
+    public static class ServiceCallbacksImplApi21 implements ServiceCallbacksApi21 {
+        final ServiceCallbacksAdapterApi21 mCallbacks;
 
-        private final IMediaBrowserServiceCallbacksAdapterApi21 mCallbacks;
-
-        ServiceCallbacksApi21(Object callbacksObj) {
-            mCallbacks = new IMediaBrowserServiceCallbacksAdapterApi21(callbacksObj);
+        ServiceCallbacksImplApi21(Object callbacksObj) {
+            mCallbacks = createCallbacks(callbacksObj);
         }
 
         public IBinder asBinder() {
@@ -88,31 +103,19 @@
         }
 
         public void onLoadChildren(String mediaId, List<Parcel> list) throws RemoteException {
-            List<MediaBrowser.MediaItem> itemList = null;
-            if (list != null) {
-                itemList = new ArrayList<>();
-                for (Parcel parcel : list) {
-                    parcel.setDataPosition(0);
-                    itemList.add(MediaBrowser.MediaItem.CREATOR.createFromParcel(parcel));
-                    parcel.recycle();
-                }
-            }
-            Object pls;
-            if (Build.VERSION.SDK_INT > 23) {
-                pls = itemList == null ? null : ParceledListSliceAdapterApi21.newInstance(itemList);
-            } else {
-                pls = itemList == null ? sNullParceledListSliceObj
-                        : ParceledListSliceAdapterApi21.newInstance(itemList);
-            }
-            mCallbacks.onLoadChildren(mediaId, pls);
+            mCallbacks.onLoadChildren(mediaId, parcelListToParceledListSliceObject(list));
+        }
+
+        ServiceCallbacksAdapterApi21 createCallbacks(Object callbacksObj) {
+            return new ServiceCallbacksAdapterApi21(callbacksObj);
         }
     }
 
     static class MediaBrowserServiceAdaptorApi21 {
-        ServiceBinderProxyApi21 mBinder;
+        Binder mBinder;
 
         public void onCreate(ServiceImplApi21 serviceImpl) {
-            mBinder = new ServiceBinderProxyApi21(serviceImpl);
+            mBinder = createServiceBinder(serviceImpl);
         }
 
         public IBinder onBind(Intent intent) {
@@ -122,39 +125,8 @@
             return null;
         }
 
-        static class ServiceBinderProxyApi21 extends IMediaBrowserServiceAdapterApi21.Stub {
-            final ServiceImplApi21 mServiceImpl;
-
-            ServiceBinderProxyApi21(ServiceImplApi21 serviceImpl) {
-                super();
-                mServiceImpl = serviceImpl;
-            }
-
-            @Override
-            public void connect(final String pkg, final Bundle rootHints, final Object callbacks) {
-                mServiceImpl.connect(pkg, rootHints, new ServiceCallbacksApi21(callbacks));
-            }
-
-            @Override
-            public void disconnect(final Object callbacks) {
-                mServiceImpl.disconnect(new ServiceCallbacksApi21(callbacks));
-            }
-
-            @Override
-            public void addSubscription(final String id, final Object callbacks) {
-                mServiceImpl.addSubscription(id, new ServiceCallbacksApi21(callbacks));
-            }
-
-            @Override
-            public void removeSubscription(final String id,
-                    final Object callbacks) {
-                mServiceImpl.removeSubscription(id, new ServiceCallbacksApi21(callbacks));
-            }
-
-            @Override
-            public void getMediaItem(final String mediaId, final ResultReceiver receiver) {
-                // No operation since this method is added in API 23.
-            }
+        protected Binder createServiceBinder(ServiceImplApi21 serviceImpl) {
+            return new ServiceBinderAdapterApi21(serviceImpl);
         }
     }
 }
diff --git a/v4/api21/android/support/v4/media/ServiceBinderAdapterApi21.java b/v4/api21/android/support/v4/media/ServiceBinderAdapterApi21.java
new file mode 100644
index 0000000..9495049
--- /dev/null
+++ b/v4/api21/android/support/v4/media/ServiceBinderAdapterApi21.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2016 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.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.Parcel;
+import android.os.RemoteException;
+
+/**
+ * A class for presenting a service binder for API 21.
+ *
+ * MediaBrowserServiceCompat should inherit a private class, MediaBrowserService.ServiceBinder,
+ * which extends IMediaBrowserService.Stub. As a way to inherit the private class,
+ * ServiceBinderAdapterApi21 implements all necessary functionalities including the codes in the
+ * auto generated hidden class, IMediaBrowserService.Stub for API 21.
+ */
+class ServiceBinderAdapterApi21 extends Binder implements IInterface {
+    // Following TRANSACTION_XXX values are synchronized with the auto generated java file
+    // from IMediaBrowserService.aidl
+    private static final int TRANSACTION_connect = IBinder.FIRST_CALL_TRANSACTION + 0;
+    private static final int TRANSACTION_disconnect = IBinder.FIRST_CALL_TRANSACTION + 1;
+    private static final int TRANSACTION_addSubscription = IBinder.FIRST_CALL_TRANSACTION + 2;
+    private static final int TRANSACTION_removeSubscription =
+            IBinder.FIRST_CALL_TRANSACTION + 3;
+
+    static final String DESCRIPTOR = "android.service.media.IMediaBrowserService";
+    final MediaBrowserServiceCompatApi21.ServiceImplApi21 mServiceImpl;
+
+    public ServiceBinderAdapterApi21(
+            MediaBrowserServiceCompatApi21.ServiceImplApi21 serviceImpl) {
+        mServiceImpl = serviceImpl;
+        attachInterface(this, DESCRIPTOR);
+    }
+
+    @Override
+    public IBinder asBinder() {
+        return this;
+    }
+
+    @Override
+    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+            throws RemoteException {
+        switch (code) {
+            case IBinder.INTERFACE_TRANSACTION: {
+                reply.writeString(DESCRIPTOR);
+                return true;
+            }
+            case TRANSACTION_connect: {
+                data.enforceInterface(DESCRIPTOR);
+                String arg0 = data.readString();
+                Bundle arg1;
+                if (data.readInt() != 0) {
+                    arg1 = Bundle.CREATOR.createFromParcel(data);
+                } else {
+                    arg1 = null;
+                }
+                Object arg2 = ServiceCallbacksAdapterApi21.Stub.asInterface(
+                        data.readStrongBinder());
+                connect(arg0, arg1, arg2);
+                return true;
+            }
+            case TRANSACTION_disconnect: {
+                data.enforceInterface(DESCRIPTOR);
+                Object arg0 = ServiceCallbacksAdapterApi21.Stub.asInterface(
+                        data.readStrongBinder());
+                disconnect(arg0);
+                return true;
+            }
+            case TRANSACTION_addSubscription: {
+                data.enforceInterface(DESCRIPTOR);
+                String arg0 = data.readString();
+                Object arg1 = ServiceCallbacksAdapterApi21.Stub.asInterface(
+                        data.readStrongBinder());
+                addSubscription(arg0, arg1);
+                return true;
+            }
+            case TRANSACTION_removeSubscription: {
+                data.enforceInterface(DESCRIPTOR);
+                String arg0 = data.readString();
+                Object arg1 = ServiceCallbacksAdapterApi21.Stub.asInterface(
+                        data.readStrongBinder());
+                removeSubscription(arg0, arg1);
+                return true;
+            }
+        }
+        return super.onTransact(code, data, reply, flags);
+    }
+
+    void connect(String pkg, Bundle rootHints, Object callbacks) {
+        mServiceImpl.connect(pkg, rootHints,
+                new MediaBrowserServiceCompatApi21.ServiceCallbacksImplApi21(callbacks));
+    }
+
+    void disconnect(Object callbacks) {
+        mServiceImpl.disconnect(
+                new MediaBrowserServiceCompatApi21.ServiceCallbacksImplApi21(callbacks));
+    }
+
+    void addSubscription(String id, Object callbacks) {
+        mServiceImpl.addSubscription(id,
+                new MediaBrowserServiceCompatApi21.ServiceCallbacksImplApi21(callbacks));
+    }
+
+    void removeSubscription(String id, Object callbacks) {
+        mServiceImpl.removeSubscription(id,
+                new MediaBrowserServiceCompatApi21.ServiceCallbacksImplApi21(callbacks));
+    }
+}
diff --git a/v4/api21/android/support/v4/media/IMediaBrowserServiceCallbacksAdapterApi21.java b/v4/api21/android/support/v4/media/ServiceCallbacksAdapterApi21.java
similarity index 96%
rename from v4/api21/android/support/v4/media/IMediaBrowserServiceCallbacksAdapterApi21.java
rename to v4/api21/android/support/v4/media/ServiceCallbacksAdapterApi21.java
index b190e16..a9136b1 100644
--- a/v4/api21/android/support/v4/media/IMediaBrowserServiceCallbacksAdapterApi21.java
+++ b/v4/api21/android/support/v4/media/ServiceCallbacksAdapterApi21.java
@@ -28,15 +28,14 @@
  * An adapter class for accessing the hidden framework classes, IMediaBrowserServiceCallbacks and
  * IMediaBrowserServiceCallbacks.Stub using reflection.
  */
-class IMediaBrowserServiceCallbacksAdapterApi21 {
-
+class ServiceCallbacksAdapterApi21 {
     Object mCallbackObject;
     private Method mAsBinderMethod;
     private Method mOnConnectMethod;
     private Method mOnConnectFailedMethod;
     private Method mOnLoadChildrenMethod;
 
-    IMediaBrowserServiceCallbacksAdapterApi21(Object callbackObject) {
+    ServiceCallbacksAdapterApi21(Object callbackObject) {
         mCallbackObject = callbackObject;
         try {
             Class theClass = Class.forName("android.service.media.IMediaBrowserServiceCallbacks");
diff --git a/v4/api23/android/support/v4/media/MediaBrowserServiceCompatApi23.java b/v4/api23/android/support/v4/media/MediaBrowserServiceCompatApi23.java
index fcaea40..ebde176 100644
--- a/v4/api23/android/support/v4/media/MediaBrowserServiceCompatApi23.java
+++ b/v4/api23/android/support/v4/media/MediaBrowserServiceCompatApi23.java
@@ -16,26 +16,17 @@
 
 package android.support.v4.media;
 
-import android.media.browse.MediaBrowser;
+import android.os.Binder;
 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);
+        void getMediaItem(String mediaId, ItemCallback cb);
     }
 
     public interface ItemCallback {
@@ -43,44 +34,8 @@
     }
 
     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);
-                    }
-                });
-            }
+        protected Binder createServiceBinder(ServiceImplApi21 serviceImpl) {
+            return new ServiceBinderAdapterApi23((ServiceImplApi23) serviceImpl);
         }
     }
 }
diff --git a/v4/api23/android/support/v4/media/ServiceBinderAdapterApi23.java b/v4/api23/android/support/v4/media/ServiceBinderAdapterApi23.java
new file mode 100644
index 0000000..ba66347
--- /dev/null
+++ b/v4/api23/android/support/v4/media/ServiceBinderAdapterApi23.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2016 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.IBinder;
+import android.os.Parcel;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.service.media.MediaBrowserService;
+import android.util.Log;
+
+/**
+ * A class for presenting a service binder for API 23.
+ *
+ * MediaBrowserServiceCompat should inherit a private class, MediaBrowserService.ServiceBinder,
+ * which extends IMediaBrowserService.Stub. As a way to inherit the private class,
+ * ServiceBinderAdapterApi21 implements all necessary functionalities including the codes in the
+ * auto generated hidden class, IMediaBrowserService.Stub for API 23.
+ */
+class ServiceBinderAdapterApi23 extends ServiceBinderAdapterApi21 {
+    private static final String TAG = "IMediaBrowserServiceAdapterApi23";
+
+    // Following TRANSACTION_XXX values are synchronized with the auto generated java file
+    // from IMediaBrowserService.aidl
+    private static final int TRANSACTION_getMediaItem = IBinder.FIRST_CALL_TRANSACTION + 4;
+
+    final MediaBrowserServiceCompatApi23.ServiceImplApi23 mServiceImpl;
+
+    public ServiceBinderAdapterApi23(
+            MediaBrowserServiceCompatApi23.ServiceImplApi23 serviceImpl) {
+        super(serviceImpl);
+        mServiceImpl = serviceImpl;
+    }
+
+    @Override
+    public IBinder asBinder() {
+        return this;
+    }
+
+    @Override
+    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+            throws RemoteException {
+        switch (code) {
+            case TRANSACTION_getMediaItem: {
+                data.enforceInterface(DESCRIPTOR);
+                String arg0 = data.readString();
+                ResultReceiver arg1;
+                if (data.readInt() != 0) {
+                    arg1 = android.os.ResultReceiver.CREATOR.createFromParcel(data);
+                } else {
+                    arg1 = null;
+                }
+                getMediaItem(arg0, arg1);
+                return true;
+            }
+        }
+        return super.onTransact(code, data, reply, flags);
+    }
+
+    void getMediaItem(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 MediaBrowserServiceCompatApi23.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 e4b309a..90232cc 100644
--- a/v4/java/android/support/v4/media/MediaBrowserCompat.java
+++ b/v4/java/android/support/v4/media/MediaBrowserCompat.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
@@ -92,9 +93,9 @@
      */
     public MediaBrowserCompat(Context context, ComponentName serviceComponent,
             ConnectionCallback callback, Bundle rootHints) {
-        if (android.os.Build.VERSION.SDK_INT >= 23) {
+        if (Build.VERSION.SDK_INT >= 23) {
             mImpl = new MediaBrowserImplApi23(context, serviceComponent, callback, rootHints);
-        } else if (android.os.Build.VERSION.SDK_INT >= 21) {
+        } else if (Build.VERSION.SDK_INT >= 21) {
             mImpl = new MediaBrowserImplApi21(context, serviceComponent, callback, rootHints);
         } else {
             mImpl = new MediaBrowserServiceImplBase(context, serviceComponent, callback, rootHints);
@@ -169,7 +170,7 @@
      *
      * @throws IllegalStateException if not connected.
      */
-     public @NonNull MediaSessionCompat.Token getSessionToken() {
+    public @NonNull MediaSessionCompat.Token getSessionToken() {
         return mImpl.getSessionToken();
     }
 
@@ -402,7 +403,7 @@
         private ConnectionCallbackInternal mConnectionCallbackInternal;
 
         public ConnectionCallback() {
-            if (android.os.Build.VERSION.SDK_INT >= 21) {
+            if (Build.VERSION.SDK_INT >= 21) {
                 mConnectionCallbackObj =
                         MediaBrowserCompatApi21.createConnectionCallback(new StubApi21());
             } else {
@@ -595,16 +596,7 @@
         private class StubApi21 implements MediaBrowserCompatApi21.SubscriptionCallback {
             @Override
             public void onChildrenLoaded(@NonNull String parentId, List<Parcel> children) {
-                List<MediaBrowserCompat.MediaItem> mediaItems = null;
-                if (children != null) {
-                    mediaItems = new ArrayList<>();
-                    for (Parcel parcel : children) {
-                        parcel.setDataPosition(0);
-                        mediaItems.add(
-                                MediaBrowserCompat.MediaItem.CREATOR.createFromParcel(parcel));
-                        parcel.recycle();
-                    }
-                }
+                List<MediaBrowserCompat.MediaItem> mediaItems = parcelListToItemList(children);
                 if (mOptions != null) {
                     SubscriptionCallbackApi21.this.onChildrenLoaded(parentId,
                             MediaBrowserCompatUtils.applyOptions(mediaItems, mOptions),
@@ -622,6 +614,20 @@
                     SubscriptionCallbackApi21.this.onError(parentId);
                 }
             }
+
+            List<MediaBrowserCompat.MediaItem> parcelListToItemList(
+                    List<Parcel> parcelList) {
+                if (parcelList == null) {
+                    return null;
+                }
+                List<MediaBrowserCompat.MediaItem> items = new ArrayList<>();
+                for (Parcel parcel : parcelList) {
+                    parcel.setDataPosition(0);
+                    items.add(MediaBrowserCompat.MediaItem.CREATOR.createFromParcel(parcel));
+                    parcel.recycle();
+                }
+                return items;
+            }
         }
     }
 
@@ -632,7 +638,7 @@
         final Object mItemCallbackObj;
 
         public ItemCallback() {
-            if (android.os.Build.VERSION.SDK_INT >= 23) {
+            if (Build.VERSION.SDK_INT >= 23) {
                 mItemCallbackObj = MediaBrowserCompatApi23.createItemCallback(new StubApi23());
             } else {
                 mItemCallbackObj = null;
diff --git a/v4/java/android/support/v4/media/MediaBrowserServiceCompat.java b/v4/java/android/support/v4/media/MediaBrowserServiceCompat.java
index e9d6311..8acaf75 100644
--- a/v4/java/android/support/v4/media/MediaBrowserServiceCompat.java
+++ b/v4/java/android/support/v4/media/MediaBrowserServiceCompat.java
@@ -435,27 +435,27 @@
         }
 
         @Override
-        public void connect(final String pkg, final Bundle rootHints,
-                final MediaBrowserServiceCompatApi21.ServiceCallbacks callbacks) {
+        public void connect(String pkg, Bundle rootHints,
+                MediaBrowserServiceCompatApi21.ServiceCallbacksApi21 callbacks) {
             mServiceImpl.connect(pkg, Binder.getCallingUid(), rootHints,
                     new ServiceCallbacksApi21(callbacks));
         }
 
         @Override
-        public void disconnect(final MediaBrowserServiceCompatApi21.ServiceCallbacks callbacks) {
+        public void disconnect(MediaBrowserServiceCompatApi21.ServiceCallbacksApi21 callbacks) {
             mServiceImpl.disconnect(new ServiceCallbacksApi21(callbacks));
         }
 
 
         @Override
         public void addSubscription(
-                final String id, final MediaBrowserServiceCompatApi21.ServiceCallbacks callbacks) {
+                String id, MediaBrowserServiceCompatApi21.ServiceCallbacksApi21 callbacks) {
             mServiceImpl.addSubscription(id, null, new ServiceCallbacksApi21(callbacks));
         }
 
         @Override
-        public void removeSubscription(final String id,
-                final MediaBrowserServiceCompatApi21.ServiceCallbacks callbacks) {
+        public void removeSubscription(
+                String id, MediaBrowserServiceCompatApi21.ServiceCallbacksApi21 callbacks) {
             mServiceImpl.removeSubscription(id, null, new ServiceCallbacksApi21(callbacks));
         }
     }
@@ -463,7 +463,7 @@
     private class ServiceImplApi23 extends ServiceImplApi21
             implements MediaBrowserServiceCompatApi23.ServiceImplApi23 {
         @Override
-        public void getMediaItem(final String mediaId,
+        public void getMediaItem(String mediaId,
                 final MediaBrowserServiceCompatApi23.ItemCallback cb) {
             ResultReceiver receiverCompat = new ResultReceiver(mHandler) {
                 @Override
@@ -540,10 +540,10 @@
     }
 
     private class ServiceCallbacksApi21 implements ServiceCallbacks {
-        final MediaBrowserServiceCompatApi21.ServiceCallbacks mCallbacks;
+        final MediaBrowserServiceCompatApi21.ServiceCallbacksApi21 mCallbacks;
         Messenger mMessenger;
 
-        ServiceCallbacksApi21(MediaBrowserServiceCompatApi21.ServiceCallbacks callbacks) {
+        ServiceCallbacksApi21(MediaBrowserServiceCompatApi21.ServiceCallbacksApi21 callbacks) {
             mCallbacks = callbacks;
         }
 
@@ -910,13 +910,13 @@
     private void performLoadItem(String itemId, final ResultReceiver receiver) {
         final Result<MediaBrowserCompat.MediaItem> result =
                 new Result<MediaBrowserCompat.MediaItem>(itemId) {
-            @Override
-            void onResultSent(MediaBrowserCompat.MediaItem item, @ResultFlags int flag) {
-                Bundle bundle = new Bundle();
-                bundle.putParcelable(KEY_MEDIA_ITEM, item);
-                receiver.send(0, bundle);
-            }
-        };
+                    @Override
+                    void onResultSent(MediaBrowserCompat.MediaItem item, @ResultFlags int flag) {
+                        Bundle bundle = new Bundle();
+                        bundle.putParcelable(KEY_MEDIA_ITEM, item);
+                        receiver.send(0, bundle);
+                    }
+                };
 
         MediaBrowserServiceCompat.this.onLoadItem(itemId, result);