ctor public MediaBrowser(android.content.Context, android.content.ComponentName, android.media.browse.MediaBrowser.ConnectionCallback, android.os.Bundle);
method public void connect();
method public void disconnect();
+ method public android.os.Bundle getExtras();
method public android.net.Uri getRoot();
method public android.media.session.MediaSession.Token getSessionToken();
method public boolean isConnected();
- method public void loadThumbnail(android.net.Uri, int, int, int, android.media.browse.MediaBrowser.ThumbnailCallback);
+ method public void loadThumbnail(android.net.Uri, int, int, android.media.browse.MediaBrowser.ThumbnailCallback);
method public void subscribe(android.net.Uri, android.media.browse.MediaBrowser.SubscriptionCallback);
method public void unsubscribe(android.net.Uri);
}
method public int describeContents();
method public android.os.Bundle getExtras();
method public int getFlags();
+ method public int getIconResId();
+ method public android.net.Uri getIconUri();
method public java.lang.CharSequence getSummary();
method public java.lang.CharSequence getTitle();
method public android.net.Uri getUri();
ctor public MediaBrowserItem.Builder(android.net.Uri, int, java.lang.CharSequence);
method public android.media.browse.MediaBrowserItem build();
method public android.media.browse.MediaBrowserItem.Builder setExtras(android.os.Bundle);
+ method public android.media.browse.MediaBrowserItem.Builder setIconResId(int);
+ method public android.media.browse.MediaBrowserItem.Builder setIconUri(android.net.Uri);
method public android.media.browse.MediaBrowserItem.Builder setSummary(java.lang.CharSequence);
}
ctor public MediaBrowserService();
method public void dump(java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
method public android.media.session.MediaSession.Token getSessionToken();
- method public void notifyChange();
method public void notifyChildrenChanged(android.net.Uri);
method public android.os.IBinder onBind(android.content.Intent);
- method public abstract android.net.Uri onGetRoot(java.lang.String, int, android.os.Bundle);
- method public abstract android.graphics.Bitmap onGetThumbnail(android.net.Uri, int, int, int);
- method public abstract java.util.List<android.media.browse.MediaBrowserItem> onLoadChildren(android.net.Uri);
+ method protected abstract android.media.browse.MediaBrowserService.BrowserRoot onGetRoot(java.lang.String, int, android.os.Bundle);
+ method protected abstract android.graphics.Bitmap onGetThumbnail(android.net.Uri, int, int);
+ method protected abstract java.util.List<android.media.browse.MediaBrowserItem> onLoadChildren(android.net.Uri);
method public void setSessionToken(android.media.session.MediaSession.Token);
field public static final java.lang.String SERVICE_ACTION = "android.media.browse.MediaBrowserService";
}
+ public static class MediaBrowserService.BrowserRoot {
+ ctor public MediaBrowserService.BrowserRoot(android.net.Uri, android.os.Bundle);
+ }
+
}
package android.media.effect {
package android.media.browse;
+import android.content.res.Configuration;
import android.media.browse.IMediaBrowserServiceCallbacks;
import android.net.Uri;
import android.os.Bundle;
void addSubscription(in Uri uri, IMediaBrowserServiceCallbacks callbacks);
void removeSubscription(in Uri uri, IMediaBrowserServiceCallbacks callbacks);
+ void loadThumbnail(in Uri uri, int width, int height, IMediaBrowserServiceCallbacks callbacks);
}
\ No newline at end of file
package android.media.browse;
import android.content.pm.ParceledListSlice;
+import android.graphics.Bitmap;
import android.media.session.MediaSession;
import android.net.Uri;
+import android.os.Bundle;
/**
* Media API allows clients to browse through hierarchy of a user’s media collection,
* Invoked when the connected has been established.
* @param root The root Uri for browsing.
* @param session The {@link MediaSession.Token media session token} that can be used to control
- * the playback of the media app.
+ * the playback of the media app.
+ * @param extra Extras returned by the media service.
*/
- void onConnect(in Uri root, in MediaSession.Token session);
+ void onConnect(in Uri root, in MediaSession.Token session, in Bundle extras);
void onConnectFailed();
void onLoadChildren(in Uri uri, in ParceledListSlice list);
+ void onLoadThumbnail(in Uri uri, int width, int height, in Bitmap bitmap);
}
package android.media.browse;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.ParceledListSlice;
+import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.media.session.MediaSession;
import android.net.Uri;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
/**
* Browses media content offered by a link MediaBrowserService.
private final Handler mHandler = new Handler();
private final ArrayMap<Uri,Subscription> mSubscriptions =
new ArrayMap<Uri, MediaBrowser.Subscription>();
+ private final ArrayMap<ThumbnailRequest, HashSet<ThumbnailCallback>> mThumbnailCallbacks =
+ new ArrayMap<ThumbnailRequest, HashSet<ThumbnailCallback>>();
private int mState = CONNECT_STATE_DISCONNECTED;
private MediaServiceConnection mServiceConnection;
private IMediaBrowserServiceCallbacks mServiceCallbacks;
private Uri mRootUri;
private MediaSession.Token mMediaSessionToken;
+ private Bundle mExtras;
/**
* Creates a media browser for the specified media browse service.
/**
* Disconnects from the media browse service.
- * @more
* After this, no more callbacks will be received.
*/
public void disconnect() {
* </p>
*
* @throws IllegalStateException if not connected.
- */
+ */
public @NonNull Uri getRoot() {
if (mState != CONNECT_STATE_CONNECTED) {
throw new IllegalStateException("getSessionToken() called while not connected (state="
}
/**
+ * Gets any extras for the media service.
+ *
+ * @throws IllegalStateException if not connected.
+ */
+ public @Nullable Bundle getExtras() {
+ if (mState != CONNECT_STATE_CONNECTED) {
+ throw new IllegalStateException("getExtras() called while not connected (state="
+ + getStateLabel(mState) + ")");
+ }
+ return mExtras;
+ }
+
+ /**
* Gets the media session token associated with the media browser.
* <p>
* Note that the session token may become invalid or change when when the
/**
* Loads the thumbnail of a media item.
*
- * @param uri The uri of the media item.
+ * @param uri The uri of the thumbnail.
* @param width The preferred width of the icon in dp.
* @param height The preferred width of the icon in dp.
- * @param density The preferred density of the icon. Must be one of the android
- * density buckets.
* @param callback The callback to receive the thumbnail.
- *
- * @throws IllegalStateException if not connected. TODO: Is this restriction necessary?
*/
- public void loadThumbnail(@NonNull Uri uri, int width, int height, int density,
- @NonNull ThumbnailCallback callback) {
- throw new RuntimeException("implement me");
+ public void loadThumbnail(final @NonNull Uri uri, final int width, final int height,
+ final @NonNull ThumbnailCallback callback) {
+ if (uri == null) {
+ throw new IllegalArgumentException("thumbnail uri cannot be null");
+ }
+ if (callback == null) {
+ throw new IllegalArgumentException("thumbnail callback cannot be null");
+ }
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ HashSet<ThumbnailCallback> callbackSet;
+ ThumbnailRequest request = new ThumbnailRequest(uri, width, height);
+ callbackSet = mThumbnailCallbacks.get(request);
+ if (callbackSet != null) {
+ callbackSet.add(callback);
+ mThumbnailCallbacks.put(request, callbackSet);
+ // same request has been sent. we will wait for the callback.
+ return;
+ }
+ callbackSet = new HashSet<ThumbnailCallback>();
+ callbackSet.add(callback);
+ mThumbnailCallbacks.put(request, callbackSet);
+ if (mState == CONNECT_STATE_CONNECTED) {
+ try {
+ mServiceBinder.loadThumbnail(uri, width, height, mServiceCallbacks);
+ } catch (RemoteException e) {
+ // Process is crashing. We will disconnect, and upon reconnect we will
+ // automatically reload the thumbnails. So nothing to do here.
+ Log.d(TAG, "loadThumbnail failed with RemoteException uri=" + uri);
+ }
+ }
+ }
+ });
}
/**
}
private final void onServiceConnected(final IMediaBrowserServiceCallbacks callback,
- final Uri root, final MediaSession.Token session) {
+ final Uri root, final MediaSession.Token session, final Bundle extra) {
mHandler.post(new Runnable() {
@Override
public void run() {
}
mRootUri = root;
mMediaSessionToken = session;
+ mExtras = extra;
mState = CONNECT_STATE_CONNECTED;
if (DBG) {
}
}
+ for (ThumbnailRequest request : mThumbnailCallbacks.keySet()) {
+ try {
+ mServiceBinder.loadThumbnail(request.uri, request.width, request.height,
+ mServiceCallbacks);
+ } catch (RemoteException e) {
+ // Process is crashing. We will disconnect, and upon reconnect we will
+ // automatically reload. So nothing to do here.
+ Log.d(TAG, "loadThumbnail failed with RemoteException uri=" + request.uri);
+ }
+ }
+
}
});
}
});
}
+ private final void onLoadThumbnail(final IMediaBrowserServiceCallbacks callback,
+ final ThumbnailRequest request, final Bitmap bitmap) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ // Check that there hasn't been a disconnect or a different
+ // ServiceConnection.
+ if (!isCurrent(callback, "onLoadThumbnail")) {
+ return;
+ }
+
+ Set<ThumbnailCallback> callbackSet = mThumbnailCallbacks.get(request);
+ if (callbackSet == null) {
+ Log.d(TAG, "onLoadThumbnail called for request=" + request +
+ " but the callback is not registered");
+ return;
+ }
+ for (ThumbnailCallback thumbnailCallback : callbackSet) {
+ thumbnailCallback.onThumbnailLoaded(request.uri, bitmap);
+ }
+ mThumbnailCallbacks.remove(request);
+ }
+ });
+ }
+
/**
* Return true if {@code callback} is the current ServiceCallbacks. Also logs if it's not.
}
}
+ private static class ThumbnailRequest {
+ Uri uri;
+ int width;
+ int height;
+ ThumbnailRequest(@NonNull Uri uri, int width, int height) {
+ if (uri == null) {
+ throw new IllegalArgumentException("thumbnail uri cannot be null");
+ }
+ this.uri = uri;
+ this.width = width;
+ this.height = height;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof ThumbnailRequest)) return false;
+
+ ThumbnailRequest that = (ThumbnailRequest) o;
+
+ if (height != that.height) return false;
+ if (width != that.width) return false;
+ if (!uri.equals(that.uri)) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = uri.hashCode();
+ result = 31 * result + width;
+ result = 31 * result + height;
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "ThumbnailRequest{" +
+ "uri=" + uri +
+ ", width=" + width +
+ ", height=" + height +
+ '}';
+ }
+ }
+
/**
* ServiceConnection to the other app.
*/
* are the initial data as requested.
*/
@Override
- public void onConnect(final Uri root, final MediaSession.Token session) {
+ public void onConnect(final Uri root, final MediaSession.Token session,
+ final Bundle extras) {
MediaBrowser mediaBrowser = mMediaBrowser.get();
if (mediaBrowser != null) {
- mediaBrowser.onServiceConnected(this, root, session);
+ mediaBrowser.onServiceConnected(this, root, session, extras);
}
}
mediaBrowser.onLoadChildren(this, uri, list);
}
}
+
+ @Override
+ public void onLoadThumbnail(final Uri uri, int width, int height, final Bitmap bitmap) {
+ MediaBrowser mediaBrowser = mMediaBrowser.get();
+ if (mediaBrowser != null) {
+ ThumbnailRequest request = new ThumbnailRequest(uri, width, height);
+ mediaBrowser.onLoadThumbnail(this, request, bitmap);
+ }
+ }
}
private static class Subscription {
*/
public final class MediaBrowserItem implements Parcelable {
private final Uri mUri;
+ private final Uri mIconUri;
+ private final int mIconResId;
private final int mFlags;
private final CharSequence mTitle;
private final CharSequence mSummary;
/**
* Initialize a MediaBrowserItem object.
*/
- private MediaBrowserItem(@NonNull Uri uri, int flags, @NonNull CharSequence title,
- CharSequence summary, Bundle extras) {
+ private MediaBrowserItem(@NonNull Uri uri, @Nullable Uri iconUri, int iconResId, int flags,
+ @NonNull CharSequence title, CharSequence summary, Bundle extras) {
if (uri == null) {
throw new IllegalArgumentException("uri can not be null");
}
throw new IllegalArgumentException("title can not be null");
}
mUri = uri;
+ mIconUri = iconUri;
+ mIconResId = iconResId;
mFlags = flags;
mTitle = title;
mSummary = summary;
*/
private MediaBrowserItem(Parcel in) {
mUri = Uri.CREATOR.createFromParcel(in);
+ mIconUri = Uri.CREATOR.createFromParcel(in);
+ mIconResId = in.readInt();
mFlags = in.readInt();
mTitle = in.readCharSequence();
if (in.readInt() != 0) {
@Override
public void writeToParcel(Parcel out, int flags) {
mUri.writeToParcel(out, flags);
+ mIconUri.writeToParcel(out, flags);
+ out.writeInt(mIconResId);
out.writeInt(mFlags);
out.writeCharSequence(mTitle);
if (mSummary != null) {
}
/**
+ * Gets the Uri of the icon.
+ */
+ public @Nullable Uri getIconUri() {
+ return mIconUri;
+ }
+
+ /**
+ * Gets the resource id of the icon.
+ */
+ public int getIconResId() {
+ return mIconResId;
+ }
+
+ /**
* Gets the flags of the item.
*/
public @Flags int getFlags() {
private final Uri mUri;
private final int mFlags;
private final CharSequence mTitle;
+ private Uri mIconUri;
+ private int mIconResId;
private CharSequence mSummary;
private Bundle mExtras;
}
/**
+ * Sets the uri of the icon.
+ * <p>
+ * If both {@link #setIconUri(Uri)} and {@link #setIconResId(int)} are called,
+ * the resource id will be used to load the icon.
+ * </p>
+ */
+ public @NonNull Builder setIconUri(@Nullable Uri iconUri) {
+ mIconUri = iconUri;
+ return this;
+ }
+
+ /**
+ * Sets the resource id of the icon.
+ */
+ public @NonNull Builder setIconResId(int resId) {
+ mIconResId = resId;
+ return this;
+ }
+
+ /**
* Sets summary of the item, or null if none.
*/
public @NonNull Builder setSummary(@Nullable CharSequence summary) {
* Builds the item.
*/
public @NonNull MediaBrowserItem build() {
- return new MediaBrowserItem(mUri, mFlags, mTitle, mSummary, mExtras);
+ return new MediaBrowserItem(mUri, mIconUri, mIconResId,
+ mFlags, mTitle, mSummary, mExtras);
}
}
}
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
+import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.media.session.MediaSession;
import android.net.Uri;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Set;
/**
* Base class for media browse services.
String pkg;
Bundle rootHints;
IMediaBrowserServiceCallbacks callbacks;
- Uri root;
+ BrowserRoot root;
HashSet<Uri> subscriptions = new HashSet();
}
} else {
try {
mConnections.put(b, connection);
- callbacks.onConnect(connection.root, mSession);
+ callbacks.onConnect(connection.root.getRootUri(),
+ mSession, connection.root.getExtras());
} catch (RemoteException ex) {
Log.w(TAG, "Calling onConnect() failed. Dropping client. "
+ "pkg=" + pkg);
}
});
}
+
+ @Override
+ public void loadThumbnail(final Uri uri, final int width, final int height,
+ final IMediaBrowserServiceCallbacks callbacks) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ Bitmap bitmap = onGetThumbnail(uri, width, height);
+ try {
+ callbacks.onLoadThumbnail(uri, width, height, bitmap);
+ } catch (RemoteException e) {
+ Log.e(TAG, "RemoteException in calling onLoadThumbnail", e);
+ }
+ }
+ });
+ }
}
@Override
}
/**
- * Called to get the root uri for browsing by a particular client.
+ * Called to get the root information for browsing by a particular client.
* <p>
* The implementation should verify that the client package has
* permission to access browse media information before returning
* for browsing, or null if none. The contents of this bundle may affect
* the information returned when browsing.
*/
- public abstract @Nullable Uri onGetRoot(@NonNull String clientPackageName, int clientUid,
- @Nullable Bundle rootHints);
+ protected abstract @Nullable BrowserRoot onGetRoot(@NonNull String clientPackageName,
+ int clientUid, @Nullable Bundle rootHints);
/**
* Called to get information about the children of a media item.
* children are to be queried.
* @return The list of children, or null if the uri is invalid.
*/
- public abstract @Nullable List<MediaBrowserItem> onLoadChildren(@NonNull Uri parentUri);
+ protected abstract @Nullable List<MediaBrowserItem> onLoadChildren(@NonNull Uri parentUri);
/**
* Called to get the thumbnail of a particular media item.
* @param uri The uri of the media item.
* @param width The requested width of the icon in dp.
* @param height The requested height of the icon in dp.
- * @param density The requested density of the icon. This is the approximate density of the
- * screen on which the icon will be displayed. This density will be one of
- * the android density buckets.
+ *
* @return The file descriptor of the thumbnail, which may then be loaded
* using a bitmap factory, or null if the item does not have a thumbnail.
*/
- public abstract @Nullable Bitmap onGetThumbnail(@NonNull Uri uri,
- int width, int height, int density);
+ protected abstract @Nullable Bitmap onGetThumbnail(@NonNull Uri uri, int width, int height);
/**
* Call to set the media session.
}
/**
- * Notifies all connected media browsers that the content of
- * the browse service has changed in some way.
- * This will cause browsers to fetch subscribed content again.
- */
- public void notifyChange() {
- throw new RuntimeException("implement me");
- }
-
- /**
* Notifies all connected media browsers that the children of
* the specified Uri have changed in some way.
* This will cause browsers to fetch subscribed content again.
* children changed.
*/
public void notifyChildrenChanged(@NonNull Uri parentUri) {
- throw new RuntimeException("implement me");
+ if (parentUri == null) {
+ throw new IllegalArgumentException("parentUri cannot be null in notifyChildrenChanged");
+ }
+ for (IBinder binder : mConnections.keySet()) {
+ ConnectionRecord connection = mConnections.get(binder);
+ Set<Uri> uris = connection.subscriptions;
+ for (Uri uri : uris) {
+ if (uri.equals(parentUri)) {
+ performLoadChildren(uri, connection);
+ break;
+ }
+ }
+ }
}
/**
+ " package=" + connection.pkg);
}
}
+
+ public static class BrowserRoot {
+ final private Uri mUri;
+ final private Bundle mExtras;
+ public BrowserRoot(@NonNull Uri uri, @Nullable Bundle extras) {
+ if (uri == null) {
+ throw new IllegalArgumentException("The root uri in BrowserRoot cannot be null. " +
+ "Use null for BrowserRoot instead.");
+ }
+ mUri = uri;
+ mExtras = extras;
+ }
+
+ Uri getRootUri() {
+ return mUri;
+ }
+
+ Bundle getExtras() {
+ return mExtras;
+ }
+ }
}
import android.media.MediaPlayer.OnPreparedListener;
import android.media.browse.MediaBrowserItem;
import android.media.browse.MediaBrowserService;
+import android.media.browse.MediaBrowserService.BrowserRoot;
import android.media.session.MediaSession;
import android.net.Uri;
import android.net.wifi.WifiManager;
}
@Override
- public Uri onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
- return BROWSE_URI;
+ protected BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) {
+ return new BrowserRoot(BROWSE_URI, null);
}
@Override
- public List<MediaBrowserItem> onLoadChildren(Uri parentUri) {
+ protected List<MediaBrowserItem> onLoadChildren(Uri parentUri) {
final ArrayList<MediaBrowserItem> results = new ArrayList();
for (int i=0; i<10; i++) {
- results.add(new MediaBrowserItem.Builder(Uri.withAppendedPath(BASE_URI, Integer.toString(i)),
- MediaBrowserItem.FLAG_BROWSABLE, "Title " + i).setSummary("Summary " + i).build());
+ results.add(new MediaBrowserItem.Builder(
+ Uri.withAppendedPath(BASE_URI, Integer.toString(i)),
+ MediaBrowserItem.FLAG_BROWSABLE, "Title " + i)
+ .setSummary("Summary " + i)
+ .build());
}
return results;
}
@Override
- public Bitmap onGetThumbnail(Uri uri, int width, int height, int density) {
+ protected Bitmap onGetThumbnail(Uri uri, int width, int height) {
return null;
}