117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen/*
217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * Copyright (C) 2014 The Android Open Source Project
317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen *
417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * Licensed under the Apache License, Version 2.0 (the "License");
517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * you may not use this file except in compliance with the License.
617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * You may obtain a copy of the License at
717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen *
817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen *      http://www.apache.org/licenses/LICENSE-2.0
917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen *
1017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * Unless required by applicable law or agreed to in writing, software
1117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * distributed under the License is distributed on an "AS IS" BASIS,
1217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * See the License for the specific language governing permissions and
1417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * limitations under the License.
1517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen */
1617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
1717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chenpackage android.media.browse;
1817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
193625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErikimport android.annotation.IntDef;
2017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chenimport android.annotation.NonNull;
21319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chenimport android.annotation.Nullable;
2217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chenimport android.content.ComponentName;
2317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chenimport android.content.Context;
2417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chenimport android.content.Intent;
2517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chenimport android.content.ServiceConnection;
2617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chenimport android.content.pm.ParceledListSlice;
273625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErikimport android.media.MediaDescription;
28c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErikimport android.media.session.MediaController;
2917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chenimport android.media.session.MediaSession;
3017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chenimport android.os.Bundle;
3117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chenimport android.os.Handler;
3217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chenimport android.os.IBinder;
333625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErikimport android.os.Parcel;
343625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErikimport android.os.Parcelable;
3517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chenimport android.os.RemoteException;
363625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErikimport android.service.media.MediaBrowserService;
373625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErikimport android.service.media.IMediaBrowserService;
383625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErikimport android.service.media.IMediaBrowserServiceCallbacks;
393625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErikimport android.text.TextUtils;
4017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chenimport android.util.ArrayMap;
4117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chenimport android.util.Log;
4217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
433625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErikimport java.lang.annotation.Retention;
443625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErikimport java.lang.annotation.RetentionPolicy;
4517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chenimport java.lang.ref.WeakReference;
4617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chenimport java.util.Collections;
4717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chenimport java.util.List;
4817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
4917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen/**
5017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * Browses media content offered by a link MediaBrowserService.
5117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * <p>
5217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * This object is not thread-safe. All calls should happen on the thread on which the browser
5317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * was constructed.
5417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen * </p>
5517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen */
5617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chenpublic final class MediaBrowser {
5717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    private static final String TAG = "MediaBrowser";
5817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    private static final boolean DBG = false;
5917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
6017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    private static final int CONNECT_STATE_DISCONNECTED = 0;
6117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    private static final int CONNECT_STATE_CONNECTING = 1;
6217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    private static final int CONNECT_STATE_CONNECTED = 2;
6317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    private static final int CONNECT_STATE_SUSPENDED = 3;
6417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
6517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    private final Context mContext;
6617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    private final ComponentName mServiceComponent;
6717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    private final ConnectionCallback mCallback;
6817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    private final Bundle mRootHints;
6917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    private final Handler mHandler = new Handler();
70c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik    private final ArrayMap<String,Subscription> mSubscriptions =
71c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik            new ArrayMap<String, MediaBrowser.Subscription>();
7217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
7317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    private int mState = CONNECT_STATE_DISCONNECTED;
7417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    private MediaServiceConnection mServiceConnection;
7517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    private IMediaBrowserService mServiceBinder;
7617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    private IMediaBrowserServiceCallbacks mServiceCallbacks;
77c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik    private String mRootId;
7817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    private MediaSession.Token mMediaSessionToken;
79319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen    private Bundle mExtras;
8017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
8117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    /**
8217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * Creates a media browser for the specified media browse service.
8317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     *
8417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * @param context The context.
8517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * @param serviceComponent The component name of the media browse service.
8617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * @param callback The connection callback.
8717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * @param rootHints An optional bundle of service-specific arguments to send
88c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik     * to the media browse service when connecting and retrieving the root id
8917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * for browsing, or null if none.  The contents of this bundle may affect
9017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * the information returned when browsing.
9117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     */
9217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    public MediaBrowser(Context context, ComponentName serviceComponent,
9317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            ConnectionCallback callback, Bundle rootHints) {
9417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        if (context == null) {
9517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            throw new IllegalArgumentException("context must not be null");
9617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        }
9717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        if (serviceComponent == null) {
9817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            throw new IllegalArgumentException("service component must not be null");
9917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        }
10017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        if (callback == null) {
10117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            throw new IllegalArgumentException("connection callback must not be null");
10217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        }
10317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        mContext = context;
10417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        mServiceComponent = serviceComponent;
10517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        mCallback = callback;
10617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        mRootHints = rootHints;
10717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    }
10817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
10917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    /**
11017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * Connects to the media browse service.
11117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * <p>
11217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * The connection callback specified in the constructor will be invoked
11317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * when the connection completes or fails.
11417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * </p>
11517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     */
11617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    public void connect() {
11717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        if (mState != CONNECT_STATE_DISCONNECTED) {
11817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            throw new IllegalStateException("connect() called while not disconnected (state="
11917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                    + getStateLabel(mState) + ")");
12017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        }
12117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        // TODO: remove this extra check.
12217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        if (DBG) {
12317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            if (mServiceConnection != null) {
12417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                throw new RuntimeException("mServiceConnection should be null. Instead it is "
12517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                        + mServiceConnection);
12617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            }
12717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        }
12817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        if (mServiceBinder != null) {
12917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            throw new RuntimeException("mServiceBinder should be null. Instead it is "
13017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                    + mServiceBinder);
13117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        }
13217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        if (mServiceCallbacks != null) {
13317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            throw new RuntimeException("mServiceCallbacks should be null. Instead it is "
13417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                    + mServiceCallbacks);
13517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        }
13617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
13717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        mState = CONNECT_STATE_CONNECTING;
13817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
13992e565f71526141cb38f864dcb4eeb54d2cbf869RoboErik        final Intent intent = new Intent(MediaBrowserService.SERVICE_INTERFACE);
14017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        intent.setComponent(mServiceComponent);
14117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
14217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        final ServiceConnection thisConnection = mServiceConnection = new MediaServiceConnection();
14317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
1442354c5eb92016a72688b80c80fb6b94ce8ac4047Yao Chen        boolean bound = false;
14517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        try {
146c8a3e0dc9d4c1e59ad250f45c2ee416beee8dd89Yao Chen            bound = mContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
14717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        } catch (Exception ex) {
14817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            Log.e(TAG, "Failed binding to service " + mServiceComponent);
1492354c5eb92016a72688b80c80fb6b94ce8ac4047Yao Chen        }
15017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
1512354c5eb92016a72688b80c80fb6b94ce8ac4047Yao Chen        if (!bound) {
15217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            // Tell them that it didn't work.  We are already on the main thread,
15317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            // but we don't want to do callbacks inside of connect().  So post it,
15417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            // and then check that we are on the same ServiceConnection.  We know
15517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            // we won't also get an onServiceConnected or onServiceDisconnected,
15617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            // so we won't be doing double callbacks.
15717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            mHandler.post(new Runnable() {
1582354c5eb92016a72688b80c80fb6b94ce8ac4047Yao Chen                @Override
1592354c5eb92016a72688b80c80fb6b94ce8ac4047Yao Chen                public void run() {
1602354c5eb92016a72688b80c80fb6b94ce8ac4047Yao Chen                    // Ensure that nobody else came in or tried to connect again.
1612354c5eb92016a72688b80c80fb6b94ce8ac4047Yao Chen                    if (thisConnection == mServiceConnection) {
1622354c5eb92016a72688b80c80fb6b94ce8ac4047Yao Chen                        forceCloseConnection();
1632354c5eb92016a72688b80c80fb6b94ce8ac4047Yao Chen                        mCallback.onConnectionFailed();
16417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                    }
1652354c5eb92016a72688b80c80fb6b94ce8ac4047Yao Chen                }
1662354c5eb92016a72688b80c80fb6b94ce8ac4047Yao Chen            });
16717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        }
16817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
16917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        if (DBG) {
17017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            Log.d(TAG, "connect...");
17117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            dump();
17217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        }
17317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    }
17417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
17517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    /**
17617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * Disconnects from the media browse service.
17717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * After this, no more callbacks will be received.
17817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     */
17917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    public void disconnect() {
18017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        // It's ok to call this any state, because allowing this lets apps not have
18117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        // to check isConnected() unnecessarily.  They won't appreciate the extra
18217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        // assertions for this.  We do everything we can here to go back to a sane state.
18317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        if (mServiceCallbacks != null) {
18417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            try {
18517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                mServiceBinder.disconnect(mServiceCallbacks);
18617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            } catch (RemoteException ex) {
18717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                // We are disconnecting anyway.  Log, just for posterity but it's not
18817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                // a big problem.
18917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                Log.w(TAG, "RemoteException during connect for " + mServiceComponent);
19017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            }
19117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        }
19217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        forceCloseConnection();
19317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
19417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        if (DBG) {
19517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            Log.d(TAG, "disconnect...");
19617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            dump();
19717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        }
19817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    }
19917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
20017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    /**
20117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * Null out the variables and unbind from the service.  This doesn't include
20217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * calling disconnect on the service, because we only try to do that in the
20317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * clean shutdown cases.
20417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * <p>
20517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * Everywhere that calls this EXCEPT for disconnect() should follow it with
20617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * a call to mCallback.onConnectionFailed().  Disconnect doesn't do that callback
20717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * for a clean shutdown, but everywhere else is a dirty shutdown and should
20817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * notify the app.
20917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     */
21017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    private void forceCloseConnection() {
21117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        if (mServiceConnection != null) {
21217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            mContext.unbindService(mServiceConnection);
21317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        }
21417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        mState = CONNECT_STATE_DISCONNECTED;
21517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        mServiceConnection = null;
21617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        mServiceBinder = null;
21717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        mServiceCallbacks = null;
218c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik        mRootId = null;
21917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        mMediaSessionToken = null;
22017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    }
22117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
22217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    /**
22317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * Returns whether the browser is connected to the service.
22417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     */
22517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    public boolean isConnected() {
22617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        return mState == CONNECT_STATE_CONNECTED;
22717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    }
22817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
22917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    /**
23051fdfa273e036d0e4f5e5c624988b33873fa3ec7Yao Chen     * Gets the service component that the media browser is connected to.
23151fdfa273e036d0e4f5e5c624988b33873fa3ec7Yao Chen     */
23251fdfa273e036d0e4f5e5c624988b33873fa3ec7Yao Chen    public @NonNull ComponentName getServiceComponent() {
23351fdfa273e036d0e4f5e5c624988b33873fa3ec7Yao Chen        if (!isConnected()) {
23451fdfa273e036d0e4f5e5c624988b33873fa3ec7Yao Chen            throw new IllegalStateException("getServiceComponent() called while not connected" +
23551fdfa273e036d0e4f5e5c624988b33873fa3ec7Yao Chen                    " (state=" + mState + ")");
23651fdfa273e036d0e4f5e5c624988b33873fa3ec7Yao Chen        }
23751fdfa273e036d0e4f5e5c624988b33873fa3ec7Yao Chen        return mServiceComponent;
23851fdfa273e036d0e4f5e5c624988b33873fa3ec7Yao Chen    }
23951fdfa273e036d0e4f5e5c624988b33873fa3ec7Yao Chen
24051fdfa273e036d0e4f5e5c624988b33873fa3ec7Yao Chen    /**
241c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik     * Gets the root id.
24217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * <p>
243c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik     * Note that the root id may become invalid or change when when the
24417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * browser is disconnected.
24517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * </p>
24617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     *
24717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * @throws IllegalStateException if not connected.
248319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen     */
249c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik    public @NonNull String getRoot() {
25051fdfa273e036d0e4f5e5c624988b33873fa3ec7Yao Chen        if (!isConnected()) {
25117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            throw new IllegalStateException("getSessionToken() called while not connected (state="
25217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                    + getStateLabel(mState) + ")");
25317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        }
254c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik        return mRootId;
25517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    }
25617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
25717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    /**
258319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen     * Gets any extras for the media service.
259319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen     *
260319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen     * @throws IllegalStateException if not connected.
261319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen     */
262319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen    public @Nullable Bundle getExtras() {
26351fdfa273e036d0e4f5e5c624988b33873fa3ec7Yao Chen        if (!isConnected()) {
264319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen            throw new IllegalStateException("getExtras() called while not connected (state="
265319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen                    + getStateLabel(mState) + ")");
266319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen        }
267319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen        return mExtras;
268319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen    }
269319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen
270319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen    /**
27117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * Gets the media session token associated with the media browser.
27217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * <p>
27317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * Note that the session token may become invalid or change when when the
27417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * browser is disconnected.
27517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * </p>
27617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     *
27717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * @return The session token for the browser, never null.
27817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     *
27917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * @throws IllegalStateException if not connected.
28017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     */
28117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     public @NonNull MediaSession.Token getSessionToken() {
28251fdfa273e036d0e4f5e5c624988b33873fa3ec7Yao Chen        if (!isConnected()) {
28317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            throw new IllegalStateException("getSessionToken() called while not connected (state="
28417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                    + mState + ")");
28517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        }
28617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        return mMediaSessionToken;
28717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    }
28817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
28917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    /**
29017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * Queries for information about the media items that are contained within
291c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik     * the specified id and subscribes to receive updates when they change.
29217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * <p>
29317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * The list of subscriptions is maintained even when not connected and is
29417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * restored after reconnection.  It is ok to subscribe while not connected
29517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * but the results will not be returned until the connection completes.
29617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * </p><p>
297c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik     * If the id is already subscribed with a different callback then the new
29817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * callback will replace the previous one.
29917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * </p>
30017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     *
301c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik     * @param parentId The id of the parent media item whose list of children
30217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * will be subscribed.
30317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * @param callback The callback to receive the list of children.
30417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     */
305c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik    public void subscribe(@NonNull String parentId, @NonNull SubscriptionCallback callback) {
30617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        // Check arguments.
307c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik        if (parentId == null) {
308c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik            throw new IllegalArgumentException("parentId is null");
30917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        }
31017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        if (callback == null) {
31117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            throw new IllegalArgumentException("callback is null");
31217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        }
31317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
31417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        // Update or create the subscription.
315c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik        Subscription sub = mSubscriptions.get(parentId);
31617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        boolean newSubscription = sub == null;
31717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        if (newSubscription) {
318c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik            sub = new Subscription(parentId);
319c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik            mSubscriptions.put(parentId, sub);
32017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        }
32117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        sub.callback = callback;
32217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
32317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        // If we are connected, tell the service that we are watching.  If we aren't
32417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        // connected, the service will be told when we connect.
32517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        if (mState == CONNECT_STATE_CONNECTED && newSubscription) {
32617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            try {
327c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik                mServiceBinder.addSubscription(parentId, mServiceCallbacks);
32817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            } catch (RemoteException ex) {
32917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                // Process is crashing.  We will disconnect, and upon reconnect we will
33017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                // automatically reregister. So nothing to do here.
331c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik                Log.d(TAG, "addSubscription failed with RemoteException parentId=" + parentId);
33217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            }
33317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        }
33417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    }
33517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
33617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    /**
337c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik     * Unsubscribes for changes to the children of the specified media id.
33817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * <p>
33917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * The query callback will no longer be invoked for results associated with
340c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik     * this id once this method returns.
34117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * </p>
34217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     *
343c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik     * @param parentId The id of the parent media item whose list of children
34417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * will be unsubscribed.
34517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     */
346c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik    public void unsubscribe(@NonNull String parentId) {
34717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        // Check arguments.
348c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik        if (parentId == null) {
349c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik            throw new IllegalArgumentException("parentId is null");
35017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        }
35117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
35217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        // Remove from our list.
353c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik        final Subscription sub = mSubscriptions.remove(parentId);
35417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
35517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        // Tell the service if necessary.
35617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        if (mState == CONNECT_STATE_CONNECTED && sub != null) {
35717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            try {
358c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik                mServiceBinder.removeSubscription(parentId, mServiceCallbacks);
35917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            } catch (RemoteException ex) {
36017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                // Process is crashing.  We will disconnect, and upon reconnect we will
36117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                // automatically reregister. So nothing to do here.
362c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik                Log.d(TAG, "removeSubscription failed with RemoteException parentId=" + parentId);
36317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            }
36417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        }
36517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    }
36617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
36717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    /**
36817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * For debugging.
36917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     */
37017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    private static String getStateLabel(int state) {
37117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        switch (state) {
37217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            case CONNECT_STATE_DISCONNECTED:
37317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                return "CONNECT_STATE_DISCONNECTED";
37417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            case CONNECT_STATE_CONNECTING:
37517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                return "CONNECT_STATE_CONNECTING";
37617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            case CONNECT_STATE_CONNECTED:
37717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                return "CONNECT_STATE_CONNECTED";
37817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            case CONNECT_STATE_SUSPENDED:
37917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                return "CONNECT_STATE_SUSPENDED";
38017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            default:
38117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                return "UNKNOWN/" + state;
38217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        }
38317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    }
38417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
38517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    private final void onServiceConnected(final IMediaBrowserServiceCallbacks callback,
386c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik            final String root, final MediaSession.Token session, final Bundle extra) {
38717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        mHandler.post(new Runnable() {
38817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            @Override
38917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            public void run() {
39017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                // Check to make sure there hasn't been a disconnect or a different
39117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                // ServiceConnection.
39217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                if (!isCurrent(callback, "onConnect")) {
39317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                    return;
39417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                }
39517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                // Don't allow them to call us twice.
39617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                if (mState != CONNECT_STATE_CONNECTING) {
39717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                    Log.w(TAG, "onConnect from service while mState="
39817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                            + getStateLabel(mState) + "... ignoring");
39917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                    return;
40017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                }
401c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik                mRootId = root;
40217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                mMediaSessionToken = session;
403319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen                mExtras = extra;
40417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                mState = CONNECT_STATE_CONNECTED;
40517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
40617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                if (DBG) {
40717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                    Log.d(TAG, "ServiceCallbacks.onConnect...");
40817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                    dump();
40917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                }
41017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                mCallback.onConnected();
41117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
41217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                // we may receive some subscriptions before we are connected, so re-subscribe
41317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                // everything now
414c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik                for (String id : mSubscriptions.keySet()) {
41517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                    try {
416c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik                        mServiceBinder.addSubscription(id, mServiceCallbacks);
41717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                    } catch (RemoteException ex) {
41817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                        // Process is crashing.  We will disconnect, and upon reconnect we will
41917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                        // automatically reregister. So nothing to do here.
420c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik                        Log.d(TAG, "addSubscription failed with RemoteException parentId=" + id);
42117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                    }
42217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                }
42317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            }
42417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        });
42517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    }
42617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
42717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    private final void onConnectionFailed(final IMediaBrowserServiceCallbacks callback) {
42817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        mHandler.post(new Runnable() {
42917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            @Override
43017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            public void run() {
43117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                Log.e(TAG, "onConnectFailed for " + mServiceComponent);
43217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
43317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                // Check to make sure there hasn't been a disconnect or a different
43417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                // ServiceConnection.
43517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                if (!isCurrent(callback, "onConnectFailed")) {
43617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                    return;
43717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                }
43817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                // Don't allow them to call us twice.
43917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                if (mState != CONNECT_STATE_CONNECTING) {
44017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                    Log.w(TAG, "onConnect from service while mState="
44117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                            + getStateLabel(mState) + "... ignoring");
44217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                    return;
44317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                }
44417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
44517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                // Clean up
44617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                forceCloseConnection();
44717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
44817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                // Tell the app.
44917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                mCallback.onConnectionFailed();
45017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            }
45117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        });
45217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    }
45317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
454c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik    private final void onLoadChildren(final IMediaBrowserServiceCallbacks callback,
455c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik            final String parentId, final ParceledListSlice list) {
45617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        mHandler.post(new Runnable() {
45717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            @Override
45817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            public void run() {
45917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                // Check that there hasn't been a disconnect or a different
46017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                // ServiceConnection.
46117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                if (!isCurrent(callback, "onLoadChildren")) {
46217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                    return;
46317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                }
46417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
4653625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik                List<MediaItem> data = list.getList();
46617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                if (DBG) {
467c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik                    Log.d(TAG, "onLoadChildren for " + mServiceComponent + " id=" + parentId);
46817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                }
46917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                if (data == null) {
47017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                    data = Collections.emptyList();
47117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                }
47217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
47317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                // Check that the subscription is still subscribed.
474c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik                final Subscription subscription = mSubscriptions.get(parentId);
47517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                if (subscription == null) {
47617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                    if (DBG) {
477c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik                        Log.d(TAG, "onLoadChildren for id that isn't subscribed id="
478c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik                                + parentId);
47917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                    }
48017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                    return;
48117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                }
48217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
48317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                // Tell the app.
484c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik                subscription.callback.onChildrenLoaded(parentId, data);
48517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            }
48617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        });
48717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    }
48817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
48917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    /**
49017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * Return true if {@code callback} is the current ServiceCallbacks.  Also logs if it's not.
49117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     */
49217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    private boolean isCurrent(IMediaBrowserServiceCallbacks callback, String funcName) {
49317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        if (mServiceCallbacks != callback) {
49417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            if (mState != CONNECT_STATE_DISCONNECTED) {
49517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                Log.i(TAG, funcName + " for " + mServiceComponent + " with mServiceConnection="
49617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                        + mServiceCallbacks + " this=" + this);
49717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            }
49817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            return false;
49917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        }
50017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        return true;
50117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    }
50217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
50317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    private ServiceCallbacks getNewServiceCallbacks() {
50417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        return new ServiceCallbacks(this);
50517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    }
50617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
50717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    /**
50817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * Log internal state.
50917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * @hide
51017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     */
51117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    void dump() {
51217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        Log.d(TAG, "MediaBrowser...");
51317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        Log.d(TAG, "  mServiceComponent=" + mServiceComponent);
51417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        Log.d(TAG, "  mCallback=" + mCallback);
51517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        Log.d(TAG, "  mRootHints=" + mRootHints);
51617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        Log.d(TAG, "  mState=" + getStateLabel(mState));
51717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        Log.d(TAG, "  mServiceConnection=" + mServiceConnection);
51817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        Log.d(TAG, "  mServiceBinder=" + mServiceBinder);
51917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        Log.d(TAG, "  mServiceCallbacks=" + mServiceCallbacks);
520c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik        Log.d(TAG, "  mRootId=" + mRootId);
52117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        Log.d(TAG, "  mMediaSessionToken=" + mMediaSessionToken);
52217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    }
52317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
5243625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik    public static class MediaItem implements Parcelable {
5253625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik        private final int mFlags;
5263625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik        private final MediaDescription mDescription;
5273625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik
5283625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik        /** @hide */
5293625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik        @Retention(RetentionPolicy.SOURCE)
5303625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik        @IntDef(flag=true, value = { FLAG_BROWSABLE, FLAG_PLAYABLE })
5313625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik        public @interface Flags { }
53217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
53317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        /**
5343625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik         * Flag: Indicates that the item has children of its own.
53517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen         */
5363625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik        public static final int FLAG_BROWSABLE = 1 << 0;
5373625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik
5383625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik        /**
5393625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik         * Flag: Indicates that the item is playable.
5403625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik         * <p>
541c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik         * The id of this item may be passed to
542c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik         * {@link MediaController.TransportControls#playFromMediaId(String, Bundle)}
5433625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik         * to start playing it.
5443625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik         * </p>
5453625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik         */
5463625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik        public static final int FLAG_PLAYABLE = 1 << 1;
5473625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik
5483625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik        /**
5493625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik         * Create a new MediaItem for use in browsing media.
5503625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik         * @param description The description of the media, which must include a
5513625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik         *            media id.
5525dfa0a5f7a6365ddf3939f29227366172979e53eRoboErik         * @param flags The flags for this item.
5533625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik         */
5545dfa0a5f7a6365ddf3939f29227366172979e53eRoboErik        public MediaItem(@NonNull MediaDescription description, @Flags int flags) {
5553625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik            if (description == null) {
5563625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik                throw new IllegalArgumentException("description cannot be null");
5573625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik            }
5583625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik            if (TextUtils.isEmpty(description.getMediaId())) {
5593625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik                throw new IllegalArgumentException("description must have a non-empty media id");
5603625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik            }
5613625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik            mFlags = flags;
5623625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik            mDescription = description;
56317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        }
56417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
56517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        /**
5663625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik         * Private constructor.
56717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen         */
5683625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik        private MediaItem(Parcel in) {
5693625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik            mFlags = in.readInt();
5703625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik            mDescription = MediaDescription.CREATOR.createFromParcel(in);
57117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        }
57217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
5733625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik        @Override
5743625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik        public int describeContents() {
5753625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik            return 0;
5763625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik        }
5773625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik
5783625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik        @Override
5793625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik        public void writeToParcel(Parcel out, int flags) {
5803625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik            out.writeInt(mFlags);
5813625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik            mDescription.writeToParcel(out, flags);
5823625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik        }
5833625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik
5843625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik        @Override
5853625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik        public String toString() {
5863625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik            final StringBuilder sb = new StringBuilder("MediaItem{");
5873625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik            sb.append("mFlags=").append(mFlags);
5883625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik            sb.append(", mDescription=").append(mDescription);
5893625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik            sb.append('}');
5903625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik            return sb.toString();
5913625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik        }
5923625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik
5933625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik        public static final Parcelable.Creator<MediaItem> CREATOR =
5943625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik                new Parcelable.Creator<MediaItem>() {
5953625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik                    @Override
5963625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik                    public MediaItem createFromParcel(Parcel in) {
5973625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik                        return new MediaItem(in);
5983625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik                    }
5993625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik
6003625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik                    @Override
6013625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik                    public MediaItem[] newArray(int size) {
6023625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik                        return new MediaItem[size];
6033625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik                    }
6043625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik                };
6053625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik
60617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        /**
6073625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik         * Gets the flags of the item.
60817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen         */
6093625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik        public @Flags int getFlags() {
6103625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik            return mFlags;
61117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        }
61217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
61317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        /**
6143625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik         * Returns whether this item is browsable.
6153625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik         * @see #FLAG_BROWSABLE
61617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen         */
6173625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik        public boolean isBrowsable() {
6183625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik            return (mFlags & FLAG_BROWSABLE) != 0;
61917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        }
62017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
62117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        /**
6223625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik         * Returns whether this item is playable.
6233625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik         * @see #FLAG_PLAYABLE
62417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen         */
6253625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik        public boolean isPlayable() {
6263625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik            return (mFlags & FLAG_PLAYABLE) != 0;
62717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        }
62817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
62917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        /**
6303625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik         * Returns the description of the media.
63117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen         */
6323625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik        public @NonNull MediaDescription getDescription() {
6333625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik            return mDescription;
63417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        }
63517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
63617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        /**
6373625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik         * Returns the media id for this item.
63817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen         */
6393625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik        public @NonNull String getMediaId() {
6403625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik            return mDescription.getMediaId();
64117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        }
64217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    }
64317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
64480bb169d0ff778ca7073486f4e2900f8479c2daaYao Chen
6453625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik    /**
6463625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik     * Callbacks for connection related events.
6473625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik     */
6483625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik    public static class ConnectionCallback {
64980bb169d0ff778ca7073486f4e2900f8479c2daaYao Chen        /**
6503625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik         * Invoked after {@link MediaBrowser#connect()} when the request has successfully completed.
65180bb169d0ff778ca7073486f4e2900f8479c2daaYao Chen         */
6523625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik        public void onConnected() {
653319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen        }
654319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen
65580bb169d0ff778ca7073486f4e2900f8479c2daaYao Chen        /**
6563625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik         * Invoked when the client is disconnected from the media browser.
65780bb169d0ff778ca7073486f4e2900f8479c2daaYao Chen         */
6583625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik        public void onConnectionSuspended() {
659319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen        }
660319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen
66180bb169d0ff778ca7073486f4e2900f8479c2daaYao Chen        /**
6623625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik         * Invoked when the connection to the media browser failed.
66380bb169d0ff778ca7073486f4e2900f8479c2daaYao Chen         */
6643625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik        public void onConnectionFailed() {
665319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen        }
6663625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik    }
667319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen
6683625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik    /**
6693625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik     * Callbacks for subscription related events.
6703625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik     */
6713625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik    public static abstract class SubscriptionCallback {
6723625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik        /**
6733625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik         * Called when the list of children is loaded or updated.
6743625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik         */
675c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik        public void onChildrenLoaded(@NonNull String parentId,
6763625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik                                     @NonNull List<MediaItem> children) {
67780bb169d0ff778ca7073486f4e2900f8479c2daaYao Chen        }
67880bb169d0ff778ca7073486f4e2900f8479c2daaYao Chen
67980bb169d0ff778ca7073486f4e2900f8479c2daaYao Chen        /**
680c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik         * Called when the id doesn't exist or other errors in subscribing.
6813625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik         * <p>
6823625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik         * If this is called, the subscription remains until {@link MediaBrowser#unsubscribe}
6833625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik         * called, because some errors may heal themselves.
6843625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik         * </p>
68580bb169d0ff778ca7073486f4e2900f8479c2daaYao Chen         */
686c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik        public void onError(@NonNull String id) {
687319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen        }
688319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen    }
689319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen
69017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    /**
69117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * ServiceConnection to the other app.
69217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     */
69317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    private class MediaServiceConnection implements ServiceConnection {
69417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        @Override
69517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        public void onServiceConnected(ComponentName name, IBinder binder) {
69617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            if (DBG) {
69717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                Log.d(TAG, "MediaServiceConnection.onServiceConnected name=" + name
69817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                        + " binder=" + binder);
69917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                dump();
70017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            }
70117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
70217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            // Make sure we are still the current connection, and that they haven't called
70317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            // disconnect().
70417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            if (!isCurrent("onServiceConnected")) {
70517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                return;
70617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            }
70717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
70817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            // Save their binder
70917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            mServiceBinder = IMediaBrowserService.Stub.asInterface(binder);
71017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
71117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            // We make a new mServiceCallbacks each time we connect so that we can drop
71217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            // responses from previous connections.
71317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            mServiceCallbacks = getNewServiceCallbacks();
71488b84178d6a8551ea1b534d8750def8dceebdc2aRoboErik            mState = CONNECT_STATE_CONNECTING;
71517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
71617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            // Call connect, which is async. When we get a response from that we will
71717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            // say that we're connected.
71817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            try {
71917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                if (DBG) {
72017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                    Log.d(TAG, "ServiceCallbacks.onConnect...");
72117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                    dump();
72217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                }
72317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                mServiceBinder.connect(mContext.getPackageName(), mRootHints, mServiceCallbacks);
72417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            } catch (RemoteException ex) {
72517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                // Connect failed, which isn't good. But the auto-reconnect on the service
72617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                // will take over and we will come back.  We will also get the
72717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                // onServiceDisconnected, which has all the cleanup code.  So let that do it.
72817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                Log.w(TAG, "RemoteException during connect for " + mServiceComponent);
72917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                if (DBG) {
73017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                    Log.d(TAG, "ServiceCallbacks.onConnect...");
73117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                    dump();
73217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                }
73317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            }
73417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        }
73517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
73617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        @Override
73717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        public void onServiceDisconnected(ComponentName name) {
73817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            if (DBG) {
73917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                Log.d(TAG, "MediaServiceConnection.onServiceDisconnected name=" + name
74017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                        + " this=" + this + " mServiceConnection=" + mServiceConnection);
74117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                dump();
74217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            }
74317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
74417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            // Make sure we are still the current connection, and that they haven't called
74517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            // disconnect().
74617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            if (!isCurrent("onServiceDisconnected")) {
74717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                return;
74817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            }
74917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
75017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            // Clear out what we set in onServiceConnected
75117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            mServiceBinder = null;
75217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            mServiceCallbacks = null;
75317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
75417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            // And tell the app that it's suspended.
75517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            mState = CONNECT_STATE_SUSPENDED;
75617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            mCallback.onConnectionSuspended();
75717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        }
75817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
75917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        /**
76017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen         * Return true if this is the current ServiceConnection.  Also logs if it's not.
76117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen         */
76217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        private boolean isCurrent(String funcName) {
76317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            if (mServiceConnection != this) {
76417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                if (mState != CONNECT_STATE_DISCONNECTED) {
76517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                    // Check mState, because otherwise this log is noisy.
76617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                    Log.i(TAG, funcName + " for " + mServiceComponent + " with mServiceConnection="
76717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                            + mServiceConnection + " this=" + this);
76817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                }
76917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                return false;
77017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            }
77117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            return true;
77217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        }
7733625bf72cb8bcf3c7f8f8cd8d708d7206824cc62RoboErik    }
77417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
77517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    /**
77617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     * Callbacks from the service.
77717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen     */
77817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    private static class ServiceCallbacks extends IMediaBrowserServiceCallbacks.Stub {
77917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        private WeakReference<MediaBrowser> mMediaBrowser;
78017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
78117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        public ServiceCallbacks(MediaBrowser mediaBrowser) {
78217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            mMediaBrowser = new WeakReference<MediaBrowser>(mediaBrowser);
78317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        }
78417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
78517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        /**
78617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen         * The other side has acknowledged our connection.  The parameters to this function
78717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen         * are the initial data as requested.
78817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen         */
78917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        @Override
790c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik        public void onConnect(final String root, final MediaSession.Token session,
791319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen                final Bundle extras) {
79217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            MediaBrowser mediaBrowser = mMediaBrowser.get();
79317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            if (mediaBrowser != null) {
794319f9a979c1c10c0c15ca50ee20e0a05e932cbb7Yao Chen                mediaBrowser.onServiceConnected(this, root, session, extras);
79517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            }
79617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        }
79717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
79817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        /**
79917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen         * The other side does not like us.  Tell the app via onConnectionFailed.
80017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen         */
80117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        @Override
80217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        public void onConnectFailed() {
80317d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            MediaBrowser mediaBrowser = mMediaBrowser.get();
80417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            if (mediaBrowser != null) {
80517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen                mediaBrowser.onConnectionFailed(this);
80617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            }
80717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        }
80817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
80917d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        @Override
810c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik        public void onLoadChildren(final String parentId, final ParceledListSlice list) {
81117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            MediaBrowser mediaBrowser = mMediaBrowser.get();
81217d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            if (mediaBrowser != null) {
813c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik                mediaBrowser.onLoadChildren(this, parentId, list);
81417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen            }
81517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        }
81617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    }
81717d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
81817d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    private static class Subscription {
819c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik        final String id;
82017d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        SubscriptionCallback callback;
82117d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen
822c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik        Subscription(String id) {
823c692d05951561e1d9fde952cc5389ec167bdb7bbRoboErik            this.id = id;
82417d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen        }
82517d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen    }
82617d47989ee53c9e54f250d29a343ba949edf0ff9Yao Chen}
827