Use Messenger instead of AIDL between MBC and MBSC

MediaBrowserCompat (MBC) and MediaBrowserServiceCompat (MBSC) can
live in separated APKs and their support lib versions can be
different. Therefore, the AIDL approach could lead to unexpected
breaks in some cases.

Bug: 22917960
Change-Id: Ie18eef8c9ea120467d40de01a9c7fa2329e82681
diff --git a/v4/api21/android/support/v4/media/MediaBrowserServiceCompatApi21.java b/v4/api21/android/support/v4/media/MediaBrowserServiceCompatApi21.java
index e2f517f..b8363af 100644
--- a/v4/api21/android/support/v4/media/MediaBrowserServiceCompatApi21.java
+++ b/v4/api21/android/support/v4/media/MediaBrowserServiceCompatApi21.java
@@ -35,18 +35,18 @@
 class MediaBrowserServiceCompatApi21 {
 
     public static Object createService() {
-        return new MediaBrowserServiceStub();
+        return new MediaBrowserServiceAdaptor();
     }
 
-    public static void onCreate(Object serviceObj, ServiceStub stub) {
-        ((MediaBrowserServiceStub) serviceObj).onCreate(stub);
+    public static void onCreate(Object serviceObj, ServiceImpl serviceImpl) {
+        ((MediaBrowserServiceAdaptor) serviceObj).onCreate(serviceImpl);
     }
 
     public static IBinder onBind(Object serviceObj, Intent intent) {
-        return ((MediaBrowserServiceStub) serviceObj).onBind(intent);
+        return ((MediaBrowserServiceAdaptor) serviceObj).onBind(intent);
     }
 
-    public interface ServiceStub {
+    public interface ServiceImpl {
         void connect(final String pkg, final Bundle rootHints, final ServiceCallbacks callbacks);
         void disconnect(final ServiceCallbacks callbacks);
         void addSubscription(final String id, final ServiceCallbacks callbacks);
@@ -95,11 +95,11 @@
         }
     }
 
-    private static class MediaBrowserServiceStub {
+    private static class MediaBrowserServiceAdaptor {
         ServiceBinderProxy mBinder;
 
-        public void onCreate(ServiceStub stub) {
-            mBinder = new ServiceBinderProxy(stub);
+        public void onCreate(ServiceImpl serviceImpl) {
+            mBinder = new ServiceBinderProxy(serviceImpl);
         }
 
         public IBinder onBind(Intent intent) {
@@ -110,39 +110,39 @@
         }
 
         private static class ServiceBinderProxy extends IMediaBrowserService.Stub {
-            private final ServiceStub mServiceStub;
+            private final ServiceImpl mServiceImpl;
 
-            ServiceBinderProxy(ServiceStub stub) {
-                mServiceStub = stub;
+            ServiceBinderProxy(ServiceImpl serviceImpl) {
+                mServiceImpl = serviceImpl;
             }
 
             @Override
             public void connect(final String pkg, final Bundle rootHints,
                     final IMediaBrowserServiceCallbacks callbacks) {
-                mServiceStub.connect(pkg, rootHints, new ServiceCallbacksApi21(callbacks));
+                mServiceImpl.connect(pkg, rootHints, new ServiceCallbacksApi21(callbacks));
             }
 
             @Override
             public void disconnect(final IMediaBrowserServiceCallbacks callbacks) {
-                mServiceStub.disconnect(new ServiceCallbacksApi21(callbacks));
+                mServiceImpl.disconnect(new ServiceCallbacksApi21(callbacks));
             }
 
 
             @Override
             public void addSubscription(final String id,
                     final IMediaBrowserServiceCallbacks callbacks) {
-                mServiceStub.addSubscription(id, new ServiceCallbacksApi21(callbacks));
+                mServiceImpl.addSubscription(id, new ServiceCallbacksApi21(callbacks));
             }
 
             @Override
             public void removeSubscription(final String id,
                     final IMediaBrowserServiceCallbacks callbacks) {
-                mServiceStub.removeSubscription(id, new ServiceCallbacksApi21(callbacks));
+                mServiceImpl.removeSubscription(id, new ServiceCallbacksApi21(callbacks));
             }
 
             @Override
             public void getMediaItem(final String mediaId, final ResultReceiver receiver) {
-                mServiceStub.getMediaItem(mediaId, receiver);
+                mServiceImpl.getMediaItem(mediaId, receiver);
             }
         }
     }
diff --git a/v4/java/android/support/v4/media/IMediaBrowserServiceCompat.aidl b/v4/java/android/support/v4/media/IMediaBrowserServiceCompat.aidl
deleted file mode 100644
index 96155cd..0000000
--- a/v4/java/android/support/v4/media/IMediaBrowserServiceCompat.aidl
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright 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.content.res.Configuration;
-import android.net.Uri;
-import android.os.Bundle;
-import android.support.v4.media.IMediaBrowserServiceCompatCallbacks;
-import android.support.v4.os.ResultReceiver;
-
-/**
- * Media API allows clients to browse through hierarchy of a user’s media collection,
- * playback a specific media entry and interact with the now playing queue.
- * @hide
- */
-oneway interface IMediaBrowserServiceCompat {
-    void connect(String pkg, in Bundle rootHints, IMediaBrowserServiceCompatCallbacks callbacks);
-    void disconnect(IMediaBrowserServiceCompatCallbacks callbacks);
-
-    void addSubscription(String uri, IMediaBrowserServiceCompatCallbacks callbacks);
-    void removeSubscription(String uri, IMediaBrowserServiceCompatCallbacks callbacks);
-    void getMediaItem(String uri, in ResultReceiver cb);
-}
diff --git a/v4/java/android/support/v4/media/IMediaBrowserServiceCompatCallbacks.aidl b/v4/java/android/support/v4/media/IMediaBrowserServiceCompatCallbacks.aidl
deleted file mode 100644
index d2c7fd9..0000000
--- a/v4/java/android/support/v4/media/IMediaBrowserServiceCompatCallbacks.aidl
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright 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.graphics.Bitmap;
-import android.os.Bundle;
-import android.support.v4.media.session.MediaSessionCompat;
-
-import java.util.List;
-
-/**
- * Media API allows clients to browse through hierarchy of a user’s media collection,
- * playback a specific media entry and interact with the now playing queue.
- * @hide
- */
-oneway interface IMediaBrowserServiceCompatCallbacks {
-    /**
-     * Invoked when the connected has been established.
-     * @param root The root media id for browsing.
-     * @param session The {@link MediaSessionCompat.Token media session token} that can be used to
-     *         control the playback of the media app.
-     * @param extra Extras returned by the media service.
-     */
-    void onConnect(String root, in MediaSessionCompat.Token session, in Bundle extras);
-    void onConnectFailed();
-    void onLoadChildren(String mediaId, in List list);
-}
diff --git a/v4/java/android/support/v4/media/MediaBrowserCompat.java b/v4/java/android/support/v4/media/MediaBrowserCompat.java
index be7c4d7..a50a059 100644
--- a/v4/java/android/support/v4/media/MediaBrowserCompat.java
+++ b/v4/java/android/support/v4/media/MediaBrowserCompat.java
@@ -23,14 +23,14 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.RemoteException;
 import android.support.annotation.IntDef;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
-import android.support.v4.media.IMediaBrowserServiceCompat;
-import android.support.v4.media.IMediaBrowserServiceCompatCallbacks;
 import android.support.v4.media.session.MediaSessionCompat;
 import android.support.v4.os.ResultReceiver;
 import android.support.v4.util.ArrayMap;
@@ -39,7 +39,6 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -53,6 +52,14 @@
  * @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 final MediaBrowserImpl mImpl;
 
     /**
@@ -478,13 +485,13 @@
         private final ComponentName mServiceComponent;
         private final ConnectionCallback mCallback;
         private final Bundle mRootHints;
-        private final Handler mHandler = new Handler();
+        private final CallbackHandler mHandler = new CallbackHandler();
         private final ArrayMap<String,Subscription> mSubscriptions = new ArrayMap<>();
 
         private int mState = CONNECT_STATE_DISCONNECTED;
         private MediaServiceConnection mServiceConnection;
-        private IMediaBrowserServiceCompat mServiceBinder;
-        private IMediaBrowserServiceCompatCallbacks mServiceCallbacks;
+        private ServiceBinderWrapper mServiceBinderWrapper;
+        private Messenger mCallbacksMessenger;
         private String mRootId;
         private MediaSessionCompat.Token mMediaSessionToken;
         private Bundle mExtras;
@@ -518,13 +525,13 @@
                             + mServiceConnection);
                 }
             }
-            if (mServiceBinder != null) {
-                throw new RuntimeException("mServiceBinder should be null. Instead it is "
-                        + mServiceBinder);
+            if (mServiceBinderWrapper != null) {
+                throw new RuntimeException("mServiceBinderWrapper should be null. Instead it is "
+                        + mServiceBinderWrapper);
             }
-            if (mServiceCallbacks != null) {
-                throw new RuntimeException("mServiceCallbacks should be null. Instead it is "
-                        + mServiceCallbacks);
+            if (mCallbacksMessenger != null) {
+                throw new RuntimeException("mCallbacksMessenger should be null. Instead it is "
+                        + mCallbacksMessenger);
             }
 
             mState = CONNECT_STATE_CONNECTING;
@@ -570,9 +577,9 @@
             // It's ok to call this any state, because allowing this lets apps not have
             // to check isConnected() unnecessarily.  They won't appreciate the extra
             // assertions for this.  We do everything we can here to go back to a sane state.
-            if (mServiceCallbacks != null) {
+            if (mCallbacksMessenger != null) {
                 try {
-                    mServiceBinder.disconnect(mServiceCallbacks);
+                    mServiceBinderWrapper.disconnect();
                 } catch (RemoteException ex) {
                     // We are disconnecting anyway.  Log, just for posterity but it's not
                     // a big problem.
@@ -603,8 +610,8 @@
             }
             mState = CONNECT_STATE_DISCONNECTED;
             mServiceConnection = null;
-            mServiceBinder = null;
-            mServiceCallbacks = null;
+            mServiceBinderWrapper = null;
+            mCallbacksMessenger = null;
             mRootId = null;
             mMediaSessionToken = null;
         }
@@ -669,7 +676,7 @@
             // connected, the service will be told when we connect.
             if (mState == CONNECT_STATE_CONNECTED) {
                 try {
-                    mServiceBinder.addSubscription(parentId, mServiceCallbacks);
+                    mServiceBinderWrapper.addSubscription(parentId);
                 } catch (RemoteException ex) {
                     // Process is crashing.  We will disconnect, and upon reconnect we will
                     // automatically reregister. So nothing to do here.
@@ -690,7 +697,7 @@
             // Tell the service if necessary.
             if (mState == CONNECT_STATE_CONNECTED && sub != null) {
                 try {
-                    mServiceBinder.removeSubscription(parentId, mServiceCallbacks);
+                    mServiceBinderWrapper.removeSubscription(parentId);
                 } catch (RemoteException ex) {
                     // Process is crashing.  We will disconnect, and upon reconnect we will
                     // automatically reregister. So nothing to do here.
@@ -735,7 +742,7 @@
                 }
             };
             try {
-                mServiceBinder.getMediaItem(mediaId, receiver);
+                mServiceBinderWrapper.getMediaItem(mediaId, receiver);
             } catch (RemoteException e) {
                 Log.i(TAG, "Remote error getting media item.");
                 mHandler.post(new Runnable() {
@@ -765,129 +772,105 @@
             }
         }
 
-        private final void onServiceConnected(final IMediaBrowserServiceCompatCallbacks callback,
-                final String root, final MediaSessionCompat.Token session, final Bundle extra) {
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    // Check to make sure there hasn't been a disconnect or a different
-                    // ServiceConnection.
-                    if (!isCurrent(callback, "onConnect")) {
-                        return;
-                    }
-                    // Don't allow them to call us twice.
-                    if (mState != CONNECT_STATE_CONNECTING) {
-                        Log.w(TAG, "onConnect from service while mState="
-                                + getStateLabel(mState) + "... ignoring");
-                        return;
-                    }
-                    mRootId = root;
-                    mMediaSessionToken = session;
-                    mExtras = extra;
-                    mState = CONNECT_STATE_CONNECTED;
+        private final void onServiceConnected(final Messenger callback, final String root,
+                final MediaSessionCompat.Token session, final Bundle extra) {
+            // Check to make sure there hasn't been a disconnect or a different ServiceConnection.
+            if (!isCurrent(callback, "onConnect")) {
+                return;
+            }
+            // Don't allow them to call us twice.
+            if (mState != CONNECT_STATE_CONNECTING) {
+                Log.w(TAG, "onConnect from service while mState=" + getStateLabel(mState)
+                        + "... ignoring");
+                return;
+            }
+            mRootId = root;
+            mMediaSessionToken = session;
+            mExtras = extra;
+            mState = CONNECT_STATE_CONNECTED;
 
-                    if (DBG) {
-                        Log.d(TAG, "ServiceCallbacks.onConnect...");
-                        dump();
-                    }
-                    mCallback.onConnected();
+            if (DBG) {
+                Log.d(TAG, "ServiceCallbacks.onConnect...");
+                dump();
+            }
+            mCallback.onConnected();
 
-                    // we may receive some subscriptions before we are connected, so re-subscribe
-                    // everything now
-                    for (String id : mSubscriptions.keySet()) {
-                        try {
-                            mServiceBinder.addSubscription(id, mServiceCallbacks);
-                        } catch (RemoteException ex) {
-                            // Process is crashing.  We will disconnect, and upon reconnect we will
-                            // automatically reregister. So nothing to do here.
-                            Log.d(TAG, "addSubscription failed with RemoteException parentId="
-                                    + id);
-                        }
-                    }
+            // we may receive some subscriptions before we are connected, so re-subscribe
+            // everything now
+            for (String id : mSubscriptions.keySet()) {
+                try {
+                    mServiceBinderWrapper.addSubscription(id);
+                } catch (RemoteException ex) {
+                    // Process is crashing.  We will disconnect, and upon reconnect we will
+                    // automatically reregister. So nothing to do here.
+                    Log.d(TAG, "addSubscription failed with RemoteException parentId=" + id);
                 }
-            });
+            }
         }
 
-        private final void onConnectionFailed(final IMediaBrowserServiceCompatCallbacks callback) {
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    Log.e(TAG, "onConnectFailed for " + mServiceComponent);
+        private final void onConnectionFailed(final Messenger callback) {
+            Log.e(TAG, "onConnectFailed for " + mServiceComponent);
 
-                    // Check to make sure there hasn't been a disconnect or a different
-                    // ServiceConnection.
-                    if (!isCurrent(callback, "onConnectFailed")) {
-                        return;
-                    }
-                    // Don't allow them to call us twice.
-                    if (mState != CONNECT_STATE_CONNECTING) {
-                        Log.w(TAG, "onConnect from service while mState="
-                                + getStateLabel(mState) + "... ignoring");
-                        return;
-                    }
+            // Check to make sure there hasn't been a disconnect or a different ServiceConnection.
+            if (!isCurrent(callback, "onConnectFailed")) {
+                return;
+            }
+            // Don't allow them to call us twice.
+            if (mState != CONNECT_STATE_CONNECTING) {
+                Log.w(TAG, "onConnect from service while mState=" + getStateLabel(mState)
+                        + "... ignoring");
+                return;
+            }
 
-                    // Clean up
-                    forceCloseConnection();
+            // Clean up
+            forceCloseConnection();
 
-                    // Tell the app.
-                    mCallback.onConnectionFailed();
-                }
-            });
+            // Tell the app.
+            mCallback.onConnectionFailed();
         }
 
-        private final void onLoadChildren(final IMediaBrowserServiceCompatCallbacks callback,
-                final String parentId, final List list) {
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    // Check that there hasn't been a disconnect or a different
-                    // ServiceConnection.
-                    if (!isCurrent(callback, "onLoadChildren")) {
-                        return;
-                    }
+        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();
-                    }
+        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;
-                    }
+        // 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);
-                }
-            });
+        // Tell the app.
+        subscription.callback.onChildrenLoaded(parentId, data);
         }
 
         /**
          * Return true if {@code callback} is the current ServiceCallbacks.  Also logs if it's not.
          */
-        private boolean isCurrent(IMediaBrowserServiceCompatCallbacks callback, String funcName) {
-            if (mServiceCallbacks != callback) {
+        private boolean isCurrent(Messenger callback, String funcName) {
+            if (mCallbacksMessenger != callback) {
                 if (mState != CONNECT_STATE_DISCONNECTED) {
-                    Log.i(TAG, funcName + " for " + mServiceComponent + " with mServiceConnection="
-                            + mServiceCallbacks + " this=" + this);
+                    Log.i(TAG, funcName + " for " + mServiceComponent + " with mCallbacksMessenger="
+                            + mCallbacksMessenger + " this=" + this);
                 }
                 return false;
             }
             return true;
         }
 
-        private ServiceCallbacks getNewServiceCallbacks() {
-            return new ServiceCallbacks(this);
-        }
-
         /**
          * Log internal state.
          * @hide
@@ -899,12 +882,56 @@
             Log.d(TAG, "  mRootHints=" + mRootHints);
             Log.d(TAG, "  mState=" + getStateLabel(mState));
             Log.d(TAG, "  mServiceConnection=" + mServiceConnection);
-            Log.d(TAG, "  mServiceBinder=" + mServiceBinder);
-            Log.d(TAG, "  mServiceCallbacks=" + mServiceCallbacks);
+            Log.d(TAG, "  mServiceBinderWrapper=" + mServiceBinderWrapper);
+            Log.d(TAG, "  mCallbacksMessenger=" + mCallbacksMessenger);
             Log.d(TAG, "  mRootId=" + mRootId);
             Log.d(TAG, "  mMediaSessionToken=" + mMediaSessionToken);
         }
 
+        private class ServiceBinderWrapper {
+            private Messenger mMessenger;
+
+            public ServiceBinderWrapper(IBinder target) {
+                mMessenger = new Messenger(target);
+            }
+
+            void connect() throws RemoteException {
+                sendRequest(MediaBrowserServiceCompat.MSG_CONNECT, mContext.getPackageName(),
+                        mRootHints, mCallbacksMessenger);
+            }
+
+            void disconnect() throws RemoteException {
+                sendRequest(MediaBrowserServiceCompat.MSG_DISCONNECT, null, null,
+                        mCallbacksMessenger);
+            }
+
+            void addSubscription(String parentId) throws RemoteException {
+                sendRequest(MediaBrowserServiceCompat.MSG_ADD_SUBSCRIPTION, parentId, null,
+                        mCallbacksMessenger);
+            }
+
+            void removeSubscription(String parentId) throws RemoteException {
+                sendRequest(MediaBrowserServiceCompat.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);
+            }
+
+            private void sendRequest(int what, Object obj, Bundle data, Messenger cbMessenger)
+                    throws RemoteException {
+                Message msg = Message.obtain();
+                msg.what = what;
+                msg.obj = obj;
+                msg.setData(data);
+                msg.replyTo = cbMessenger;
+                mMessenger.send(msg);
+            }
+        }
+
         /**
          * ServiceConnection to the other app.
          */
@@ -924,11 +951,12 @@
                 }
 
                 // Save their binder
-                mServiceBinder = IMediaBrowserServiceCompat.Stub.asInterface(binder);
+                mServiceBinderWrapper = new ServiceBinderWrapper(binder);
 
                 // We make a new mServiceCallbacks each time we connect so that we can drop
                 // responses from previous connections.
-                mServiceCallbacks = getNewServiceCallbacks();
+                mCallbacksMessenger = new Messenger(mHandler);
+
                 mState = CONNECT_STATE_CONNECTING;
 
                 // Call connect, which is async. When we get a response from that we will
@@ -938,8 +966,7 @@
                         Log.d(TAG, "ServiceCallbacks.onConnect...");
                         dump();
                     }
-                    mServiceBinder.connect(
-                            mContext.getPackageName(), mRootHints, mServiceCallbacks);
+                    mServiceBinderWrapper.connect();
                 } catch (RemoteException ex) {
                     // Connect failed, which isn't good. But the auto-reconnect on the service
                     // will take over and we will come back.  We will also get the
@@ -967,8 +994,8 @@
                 }
 
                 // Clear out what we set in onServiceConnected
-                mServiceBinder = null;
-                mServiceCallbacks = null;
+                mServiceBinderWrapper = null;
+                mCallbacksMessenger = null;
 
                 // And tell the app that it's suspended.
                 mState = CONNECT_STATE_SUSPENDED;
@@ -991,45 +1018,27 @@
             }
         }
 
-        /**
-         * Callbacks from the service.
-         */
-        private static class ServiceCallbacks extends IMediaBrowserServiceCompatCallbacks.Stub {
-            private WeakReference<MediaBrowserImplBase> mMediaBrowser;
-
-            public ServiceCallbacks(MediaBrowserImplBase mediaBrowser) {
-                mMediaBrowser = new WeakReference<>(mediaBrowser);
-            }
-
-            /**
-             * The other side has acknowledged our connection.  The parameters to this function
-             * are the initial data as requested.
-             */
+        private class CallbackHandler extends Handler {
             @Override
-            public void onConnect(final String root, final MediaSessionCompat.Token session,
-                    final Bundle extras) {
-                MediaBrowserImplBase mediaBrowser = mMediaBrowser.get();
-                if (mediaBrowser != null) {
-                    mediaBrowser.onServiceConnected(this, root, session, extras);
-                }
-            }
-
-            /**
-             * The other side does not like us.  Tell the app via onConnectionFailed.
-             */
-            @Override
-            public void onConnectFailed() {
-                MediaBrowserImplBase mediaBrowser = mMediaBrowser.get();
-                if (mediaBrowser != null) {
-                    mediaBrowser.onConnectionFailed(this);
-                }
-            }
-
-            @Override
-            public void onLoadChildren(final String parentId, final List list) {
-                MediaBrowserImplBase mediaBrowser = mMediaBrowser.get();
-                if (mediaBrowser != null) {
-                    mediaBrowser.onLoadChildren(this, parentId, list);
+            public void handleMessage(Message msg) {
+                Bundle data = msg.getData();
+                switch (msg.what) {
+                    case MSG_ON_CONNECT:
+                        onServiceConnected(mCallbacksMessenger, (String) msg.obj,
+                                (MediaSessionCompat.Token) data.getParcelable(
+                                        MediaBrowserServiceCompat.DATA_MEDIA_SESSION_TOKEN),
+                                data.getBundle(MediaBrowserServiceCompat.DATA_EXTRAS));
+                        break;
+                    case MSG_ON_CONNECT_FAILED:
+                        onConnectionFailed(mCallbacksMessenger);
+                        break;
+                    case MSG_ON_LOAD_CHILDREN:
+                        onLoadChildren(mCallbacksMessenger,  (String) msg.obj,
+                                data.getParcelableArrayList(
+                                        MediaBrowserServiceCompat.DATA_MEDIA_ITEM_LIST));
+                        break;
+                    default:
+                        super.handleMessage(msg);
                 }
             }
         }
diff --git a/v4/java/android/support/v4/media/MediaBrowserServiceCompat.java b/v4/java/android/support/v4/media/MediaBrowserServiceCompat.java
index 5d5f1f7..ded7e7e 100644
--- a/v4/java/android/support/v4/media/MediaBrowserServiceCompat.java
+++ b/v4/java/android/support/v4/media/MediaBrowserServiceCompat.java
@@ -24,6 +24,8 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
 import android.os.Parcel;
 import android.os.RemoteException;
 import android.support.annotation.NonNull;
@@ -66,6 +68,16 @@
     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;
 
     /**
@@ -81,27 +93,26 @@
     public static final String KEY_MEDIA_ITEM = "media_item";
 
     private final ArrayMap<IBinder, ConnectionRecord> mConnections = new ArrayMap();
-    private final Handler mHandler = new Handler();
+    private final ServiceHandler mHandler = new ServiceHandler();
     MediaSessionCompat.Token mSession;
 
     interface MediaBrowserServiceImpl {
-        public void onCreate();
+        void onCreate();
         IBinder onBind(Intent intent);
     }
 
     class MediaBrowserServiceImplBase implements MediaBrowserServiceImpl {
-        private ServiceBinderCompat mBinder;
+        private Messenger mMessenger;
 
         @Override
         public void onCreate() {
-            mBinder = new ServiceBinderCompat(new ServiceStub());
+            mMessenger = new Messenger(mHandler);
         }
 
         @Override
         public IBinder onBind(Intent intent) {
-            // STOPSHIP: Use messenger or version management for further extension
             if (SERVICE_INTERFACE.equals(intent.getAction())) {
-                return mBinder;
+                return mMessenger.getBinder();
             }
             return null;
         }
@@ -114,7 +125,7 @@
         public void onCreate() {
             mServiceObj = MediaBrowserServiceCompatApi21.createService();
             MediaBrowserServiceCompatApi21.onCreate(mServiceObj,
-                    new ServiceStubApi21(new ServiceStub()));
+                    new ServiceImplApi21(mHandler.getServiceImpl()));
         }
 
         @Override
@@ -123,6 +134,49 @@
         }
     }
 
+    private final class ServiceHandler extends Handler {
+        private final ServiceImpl mServiceImpl = new ServiceImpl();
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_CONNECT:
+                    mServiceImpl.connect((String) msg.obj, msg.getData(),
+                            new ServiceCallbacksCompat(msg.replyTo));
+                    break;
+                case MSG_DISCONNECT:
+                    mServiceImpl.disconnect(new ServiceCallbacksCompat(msg.replyTo));
+                    break;
+                case MSG_ADD_SUBSCRIPTION:
+                    mServiceImpl.addSubscription((String) msg.obj,
+                            new ServiceCallbacksCompat(msg.replyTo));
+                    break;
+                case MSG_REMOVE_SUBSCRIPTION:
+                    mServiceImpl.removeSubscription((String) msg.obj,
+                            new ServiceCallbacksCompat(msg.replyTo));
+                    break;
+                case MSG_GET_MEDIA_ITEM:
+                    mServiceImpl.getMediaItem((String) msg.obj, (ResultReceiver) msg.getData()
+                            .getParcelable(MediaBrowserCompat.DATA_RESULT_RECEIVER));
+                    break;
+                default:
+                    super.handleMessage(msg);
+            }
+        }
+
+        public void postOrRun(Runnable r) {
+            if (Thread.currentThread() == getLooper().getThread()) {
+                r.run();
+            } else {
+                post(r);
+            }
+        }
+
+        public ServiceImpl getServiceImpl() {
+            return mServiceImpl;
+        }
+    }
+
     /**
      * All the info about a connection.
      */
@@ -195,7 +249,7 @@
         }
     }
 
-    private class ServiceStub {
+    private class ServiceImpl {
         public void connect(final String pkg, final Bundle rootHints,
                 final ServiceCallbacks callbacks) {
 
@@ -205,7 +259,7 @@
                         + " package=" + pkg);
             }
 
-            mHandler.post(new Runnable() {
+            mHandler.postOrRun(new Runnable() {
                 @Override
                 public void run() {
                     final IBinder b = callbacks.asBinder();
@@ -249,7 +303,7 @@
         }
 
         public void disconnect(final ServiceCallbacks callbacks) {
-            mHandler.post(new Runnable() {
+            mHandler.postOrRun(new Runnable() {
                 @Override
                 public void run() {
                     final IBinder b = callbacks.asBinder();
@@ -265,7 +319,7 @@
 
 
         public void addSubscription(final String id, final ServiceCallbacks callbacks) {
-            mHandler.post(new Runnable() {
+            mHandler.postOrRun(new Runnable() {
                 @Override
                 public void run() {
                     final IBinder b = callbacks.asBinder();
@@ -284,7 +338,7 @@
         }
 
         public void removeSubscription(final String id, final ServiceCallbacks callbacks) {
-            mHandler.post(new Runnable() {
+            mHandler.postOrRun(new Runnable() {
                 @Override
                 public void run() {
                     final IBinder b = callbacks.asBinder();
@@ -308,7 +362,7 @@
                 return;
             }
 
-            mHandler.post(new Runnable() {
+            mHandler.postOrRun(new Runnable() {
                 @Override
                 public void run() {
                     performLoadItem(mediaId, receiver);
@@ -317,73 +371,35 @@
         }
     }
 
-    private class ServiceBinderCompat extends IMediaBrowserServiceCompat.Stub {
-        final ServiceStub mServiceStub;
+    private class ServiceImplApi21 implements MediaBrowserServiceCompatApi21.ServiceImpl {
+        final ServiceImpl mServiceImpl;
 
-        ServiceBinderCompat(ServiceStub binder) {
-            mServiceStub = binder;
-        }
-
-        @Override
-        public void connect(final String pkg, final Bundle rootHints,
-                final IMediaBrowserServiceCompatCallbacks callbacks) {
-            mServiceStub.connect(pkg, rootHints, new ServiceCallbacksCompat(callbacks));
-        }
-
-        @Override
-        public void disconnect(final IMediaBrowserServiceCompatCallbacks callbacks) {
-            mConnections.get(callbacks.asBinder());
-            mServiceStub.disconnect(new ServiceCallbacksCompat(callbacks));
-        }
-
-
-        @Override
-        public void addSubscription(
-                final String id, final IMediaBrowserServiceCompatCallbacks callbacks) {
-            mServiceStub.addSubscription(id, new ServiceCallbacksCompat(callbacks));
-        }
-
-        @Override
-        public void removeSubscription(final String id,
-                final IMediaBrowserServiceCompatCallbacks callbacks) {
-            mServiceStub.removeSubscription(id, new ServiceCallbacksCompat(callbacks));
-        }
-
-        @Override
-        public void getMediaItem(final String mediaId, final ResultReceiver receiver) {
-            mServiceStub.getMediaItem(mediaId, receiver);
-        }
-    }
-
-    private class ServiceStubApi21 implements MediaBrowserServiceCompatApi21.ServiceStub {
-        final ServiceStub mBinder;
-
-        ServiceStubApi21(ServiceStub binder) {
-            mBinder = binder;
+        ServiceImplApi21(ServiceImpl serviceImpl) {
+            mServiceImpl = serviceImpl;
         }
 
         @Override
         public void connect(final String pkg, final Bundle rootHints,
                 final MediaBrowserServiceCompatApi21.ServiceCallbacks callbacks) {
-            mBinder.connect(pkg, rootHints, new ServiceCallbacksApi21(callbacks));
+            mServiceImpl.connect(pkg, rootHints, new ServiceCallbacksApi21(callbacks));
         }
 
         @Override
         public void disconnect(final MediaBrowserServiceCompatApi21.ServiceCallbacks callbacks) {
-            mBinder.disconnect(new ServiceCallbacksApi21(callbacks));
+            mServiceImpl.disconnect(new ServiceCallbacksApi21(callbacks));
         }
 
 
         @Override
         public void addSubscription(
                 final String id, final MediaBrowserServiceCompatApi21.ServiceCallbacks callbacks) {
-            mBinder.addSubscription(id, new ServiceCallbacksApi21(callbacks));
+            mServiceImpl.addSubscription(id, new ServiceCallbacksApi21(callbacks));
         }
 
         @Override
         public void removeSubscription(final String id,
                 final MediaBrowserServiceCompatApi21.ServiceCallbacks callbacks) {
-            mBinder.removeSubscription(id, new ServiceCallbacksApi21(callbacks));
+            mServiceImpl.removeSubscription(id, new ServiceCallbacksApi21(callbacks));
         }
 
         @Override
@@ -394,7 +410,7 @@
                     receiver.send(resultCode, resultData);
                 }
             };
-            mBinder.getMediaItem(mediaId, receiverCompat);
+            mServiceImpl.getMediaItem(mediaId, receiverCompat);
         }
     }
 
@@ -408,28 +424,42 @@
     }
 
     private class ServiceCallbacksCompat implements ServiceCallbacks {
-        final IMediaBrowserServiceCompatCallbacks mCallbacks;
+        final Messenger mCallbacks;
 
-        ServiceCallbacksCompat(IMediaBrowserServiceCompatCallbacks callbacks) {
+        ServiceCallbacksCompat(Messenger callbacks) {
             mCallbacks = callbacks;
         }
 
         public IBinder asBinder() {
-            return mCallbacks.asBinder();
+            return mCallbacks.getBinder();
         }
 
         public void onConnect(String root, MediaSessionCompat.Token session, Bundle extras)
                 throws RemoteException {
-            mCallbacks.onConnect(root, session, extras);
+            Bundle data = new Bundle();
+            data.putParcelable(DATA_MEDIA_SESSION_TOKEN, session);
+            data.putBundle(DATA_EXTRAS, extras);
+            sendRequest(MediaBrowserCompat.MSG_ON_CONNECT, root, data);
         }
 
         public void onConnectFailed() throws RemoteException {
-            mCallbacks.onConnectFailed();
+            sendRequest(MediaBrowserCompat.MSG_ON_CONNECT_FAILED, null, null);
         }
 
         public void onLoadChildren(String mediaId, List<MediaBrowserCompat.MediaItem> list)
                 throws RemoteException {
-            mCallbacks.onLoadChildren(mediaId, list);
+            Bundle data = new Bundle();
+            data.putParcelableArrayList(DATA_MEDIA_ITEM_LIST,
+                    list instanceof ArrayList ? (ArrayList) list : new ArrayList<>(list));
+            sendRequest(MediaBrowserCompat.MSG_ON_LOAD_CHILDREN, mediaId, data);
+        }
+
+        private void sendRequest(int what, Object obj, Bundle data) throws RemoteException {
+            Message msg = Message.obtain();
+            msg.what = what;
+            msg.obj = obj;
+            msg.setData(data);
+            mCallbacks.send(msg);
         }
     }