FileDocCategorySizeDatePackage
MediaBrowser.javaAPI DocAndroid 5.1 API29720Thu Mar 12 22:22:30 GMT 2015android.media.browse

MediaBrowser

public final class MediaBrowser extends Object
Browses media content offered by a link MediaBrowserService.

This object is not thread-safe. All calls should happen on the thread on which the browser was constructed.

Fields Summary
private static final String
TAG
private static final boolean
DBG
private static final int
CONNECT_STATE_DISCONNECTED
private static final int
CONNECT_STATE_CONNECTING
private static final int
CONNECT_STATE_CONNECTED
private static final int
CONNECT_STATE_SUSPENDED
private final android.content.Context
mContext
private final android.content.ComponentName
mServiceComponent
private final ConnectionCallback
mCallback
private final android.os.Bundle
mRootHints
private final android.os.Handler
mHandler
private final android.util.ArrayMap
mSubscriptions
private int
mState
private MediaServiceConnection
mServiceConnection
private android.service.media.IMediaBrowserService
mServiceBinder
private android.service.media.IMediaBrowserServiceCallbacks
mServiceCallbacks
private String
mRootId
private MediaSession.Token
mMediaSessionToken
private android.os.Bundle
mExtras
Constructors Summary
public MediaBrowser(android.content.Context context, android.content.ComponentName serviceComponent, ConnectionCallback callback, android.os.Bundle rootHints)
Creates a media browser for the specified media browse service.

param
context The context.
param
serviceComponent The component name of the media browse service.
param
callback The connection callback.
param
rootHints An optional bundle of service-specific arguments to send to the media browse service when connecting and retrieving the root id for browsing, or null if none. The contents of this bundle may affect the information returned when browsing.


                                                                               
        
                
        if (context == null) {
            throw new IllegalArgumentException("context must not be null");
        }
        if (serviceComponent == null) {
            throw new IllegalArgumentException("service component must not be null");
        }
        if (callback == null) {
            throw new IllegalArgumentException("connection callback must not be null");
        }
        mContext = context;
        mServiceComponent = serviceComponent;
        mCallback = callback;
        mRootHints = rootHints;
    
Methods Summary
public voidconnect()
Connects to the media browse service.

The connection callback specified in the constructor will be invoked when the connection completes or fails.

        if (mState != CONNECT_STATE_DISCONNECTED) {
            throw new IllegalStateException("connect() called while not disconnected (state="
                    + getStateLabel(mState) + ")");
        }
        // TODO: remove this extra check.
        if (DBG) {
            if (mServiceConnection != null) {
                throw new RuntimeException("mServiceConnection should be null. Instead it is "
                        + mServiceConnection);
            }
        }
        if (mServiceBinder != null) {
            throw new RuntimeException("mServiceBinder should be null. Instead it is "
                    + mServiceBinder);
        }
        if (mServiceCallbacks != null) {
            throw new RuntimeException("mServiceCallbacks should be null. Instead it is "
                    + mServiceCallbacks);
        }

        mState = CONNECT_STATE_CONNECTING;

        final Intent intent = new Intent(MediaBrowserService.SERVICE_INTERFACE);
        intent.setComponent(mServiceComponent);

        final ServiceConnection thisConnection = mServiceConnection = new MediaServiceConnection();

        boolean bound = false;
        try {
            bound = mContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
        } catch (Exception ex) {
            Log.e(TAG, "Failed binding to service " + mServiceComponent);
        }

        if (!bound) {
            // Tell them that it didn't work.  We are already on the main thread,
            // but we don't want to do callbacks inside of connect().  So post it,
            // and then check that we are on the same ServiceConnection.  We know
            // we won't also get an onServiceConnected or onServiceDisconnected,
            // so we won't be doing double callbacks.
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    // Ensure that nobody else came in or tried to connect again.
                    if (thisConnection == mServiceConnection) {
                        forceCloseConnection();
                        mCallback.onConnectionFailed();
                    }
                }
            });
        }

        if (DBG) {
            Log.d(TAG, "connect...");
            dump();
        }
    
public voiddisconnect()
Disconnects from the media browse service. After this, no more callbacks will be received.

        // 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) {
            try {
                mServiceBinder.disconnect(mServiceCallbacks);
            } catch (RemoteException ex) {
                // We are disconnecting anyway.  Log, just for posterity but it's not
                // a big problem.
                Log.w(TAG, "RemoteException during connect for " + mServiceComponent);
            }
        }
        forceCloseConnection();

        if (DBG) {
            Log.d(TAG, "disconnect...");
            dump();
        }
    
voiddump()
Log internal state.

hide

        Log.d(TAG, "MediaBrowser...");
        Log.d(TAG, "  mServiceComponent=" + mServiceComponent);
        Log.d(TAG, "  mCallback=" + mCallback);
        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, "  mRootId=" + mRootId);
        Log.d(TAG, "  mMediaSessionToken=" + mMediaSessionToken);
    
private voidforceCloseConnection()
Null out the variables and unbind from the service. This doesn't include calling disconnect on the service, because we only try to do that in the clean shutdown cases.

Everywhere that calls this EXCEPT for disconnect() should follow it with a call to mCallback.onConnectionFailed(). Disconnect doesn't do that callback for a clean shutdown, but everywhere else is a dirty shutdown and should notify the app.

        if (mServiceConnection != null) {
            mContext.unbindService(mServiceConnection);
        }
        mState = CONNECT_STATE_DISCONNECTED;
        mServiceConnection = null;
        mServiceBinder = null;
        mServiceCallbacks = null;
        mRootId = null;
        mMediaSessionToken = null;
    
public android.os.BundlegetExtras()
Gets any extras for the media service.

throws
IllegalStateException if not connected.

        if (!isConnected()) {
            throw new IllegalStateException("getExtras() called while not connected (state="
                    + getStateLabel(mState) + ")");
        }
        return mExtras;
    
private android.media.browse.MediaBrowser$ServiceCallbacksgetNewServiceCallbacks()

        return new ServiceCallbacks(this);
    
public java.lang.StringgetRoot()
Gets the root id.

Note that the root id may become invalid or change when when the browser is disconnected.

throws
IllegalStateException if not connected.

        if (!isConnected()) {
            throw new IllegalStateException("getSessionToken() called while not connected (state="
                    + getStateLabel(mState) + ")");
        }
        return mRootId;
    
public android.content.ComponentNamegetServiceComponent()
Gets the service component that the media browser is connected to.

        if (!isConnected()) {
            throw new IllegalStateException("getServiceComponent() called while not connected" +
                    " (state=" + mState + ")");
        }
        return mServiceComponent;
    
public MediaSession.TokengetSessionToken()
Gets the media session token associated with the media browser.

Note that the session token may become invalid or change when when the browser is disconnected.

return
The session token for the browser, never null.
throws
IllegalStateException if not connected.

        if (!isConnected()) {
            throw new IllegalStateException("getSessionToken() called while not connected (state="
                    + mState + ")");
        }
        return mMediaSessionToken;
    
private static java.lang.StringgetStateLabel(int state)
For debugging.

        switch (state) {
            case CONNECT_STATE_DISCONNECTED:
                return "CONNECT_STATE_DISCONNECTED";
            case CONNECT_STATE_CONNECTING:
                return "CONNECT_STATE_CONNECTING";
            case CONNECT_STATE_CONNECTED:
                return "CONNECT_STATE_CONNECTED";
            case CONNECT_STATE_SUSPENDED:
                return "CONNECT_STATE_SUSPENDED";
            default:
                return "UNKNOWN/" + state;
        }
    
public booleanisConnected()
Returns whether the browser is connected to the service.

        return mState == CONNECT_STATE_CONNECTED;
    
private booleanisCurrent(android.service.media.IMediaBrowserServiceCallbacks callback, java.lang.String funcName)
Return true if {@code callback} is the current ServiceCallbacks. Also logs if it's not.

        if (mServiceCallbacks != callback) {
            if (mState != CONNECT_STATE_DISCONNECTED) {
                Log.i(TAG, funcName + " for " + mServiceComponent + " with mServiceConnection="
                        + mServiceCallbacks + " this=" + this);
            }
            return false;
        }
        return true;
    
private final voidonConnectionFailed(android.service.media.IMediaBrowserServiceCallbacks callback)

        mHandler.post(new Runnable() {
            @Override
            public void run() {
                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;
                }

                // Clean up
                forceCloseConnection();

                // Tell the app.
                mCallback.onConnectionFailed();
            }
        });
    
private final voidonLoadChildren(android.service.media.IMediaBrowserServiceCallbacks callback, java.lang.String parentId, android.content.pm.ParceledListSlice 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;
                }

                List<MediaItem> data = list.getList();
                if (DBG) {
                    Log.d(TAG, "onLoadChildren for " + mServiceComponent + " id=" + parentId);
                }
                if (data == null) {
                    data = Collections.emptyList();
                }

                // Check that the subscription is still subscribed.
                final Subscription subscription = mSubscriptions.get(parentId);
                if (subscription == null) {
                    if (DBG) {
                        Log.d(TAG, "onLoadChildren for id that isn't subscribed id="
                                + parentId);
                    }
                    return;
                }

                // Tell the app.
                subscription.callback.onChildrenLoaded(parentId, data);
            }
        });
    
private final voidonServiceConnected(android.service.media.IMediaBrowserServiceCallbacks callback, java.lang.String root, MediaSession.Token session, android.os.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;

                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);
                    }
                }
            }
        });
    
public voidsubscribe(java.lang.String parentId, android.media.browse.MediaBrowser$SubscriptionCallback callback)
Queries for information about the media items that are contained within the specified id and subscribes to receive updates when they change.

The list of subscriptions is maintained even when not connected and is restored after reconnection. It is ok to subscribe while not connected but the results will not be returned until the connection completes.

If the id is already subscribed with a different callback then the new callback will replace the previous one.

param
parentId The id of the parent media item whose list of children will be subscribed.
param
callback The callback to receive the list of children.

        // Check arguments.
        if (parentId == null) {
            throw new IllegalArgumentException("parentId is null");
        }
        if (callback == null) {
            throw new IllegalArgumentException("callback is null");
        }

        // Update or create the subscription.
        Subscription sub = mSubscriptions.get(parentId);
        boolean newSubscription = sub == null;
        if (newSubscription) {
            sub = new Subscription(parentId);
            mSubscriptions.put(parentId, sub);
        }
        sub.callback = callback;

        // If we are connected, tell the service that we are watching.  If we aren't
        // connected, the service will be told when we connect.
        if (mState == CONNECT_STATE_CONNECTED && newSubscription) {
            try {
                mServiceBinder.addSubscription(parentId, 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=" + parentId);
            }
        }
    
public voidunsubscribe(java.lang.String parentId)
Unsubscribes for changes to the children of the specified media id.

The query callback will no longer be invoked for results associated with this id once this method returns.

param
parentId The id of the parent media item whose list of children will be unsubscribed.

        // Check arguments.
        if (parentId == null) {
            throw new IllegalArgumentException("parentId is null");
        }

        // Remove from our list.
        final Subscription sub = mSubscriptions.remove(parentId);

        // Tell the service if necessary.
        if (mState == CONNECT_STATE_CONNECTED && sub != null) {
            try {
                mServiceBinder.removeSubscription(parentId, 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, "removeSubscription failed with RemoteException parentId=" + parentId);
            }
        }