OSDN Git Service

Add thumbnail related api.
authorYao Chen <yaochen@google.com>
Tue, 22 Jul 2014 06:36:24 +0000 (23:36 -0700)
committerYao Chen <yaochen@google.com>
Wed, 23 Jul 2014 00:05:04 +0000 (17:05 -0700)
Change-Id: Ifcb06bbaf8f37df367f130124cbcef065fa25766

api/current.txt
media/java/android/media/browse/IMediaBrowserService.aidl
media/java/android/media/browse/IMediaBrowserServiceCallbacks.aidl
media/java/android/media/browse/MediaBrowser.java
media/java/android/media/browse/MediaBrowserItem.java
media/java/android/media/browse/MediaBrowserService.java
tests/MusicServiceDemo/src/com/example/android/musicservicedemo/BrowserService.java

index a407abc..2b2ede4 100644 (file)
@@ -16164,10 +16164,11 @@ package android.media.browse {
     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);
   }
@@ -16195,6 +16196,8 @@ package android.media.browse {
     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();
@@ -16210,6 +16213,8 @@ package android.media.browse {
     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);
   }
 
@@ -16217,16 +16222,19 @@ package android.media.browse {
     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 {
index 4b2cb9d..177bd1b 100644 (file)
@@ -2,6 +2,7 @@
 
 package android.media.browse;
 
+import android.content.res.Configuration;
 import android.media.browse.IMediaBrowserServiceCallbacks;
 import android.net.Uri;
 import android.os.Bundle;
@@ -17,4 +18,5 @@ oneway interface IMediaBrowserService {
 
     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
index ead7624..1f03a1a 100644 (file)
@@ -3,8 +3,10 @@
 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,
@@ -16,9 +18,11 @@ oneway interface IMediaBrowserServiceCallbacks {
      * 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);
 }
index beec5f9..d17f95d 100644 (file)
 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;
@@ -36,7 +38,9 @@ import java.lang.ref.WeakReference;
 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.
@@ -61,6 +65,8 @@ public final class MediaBrowser {
     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;
@@ -68,6 +74,7 @@ public final class MediaBrowser {
     private IMediaBrowserServiceCallbacks mServiceCallbacks;
     private Uri mRootUri;
     private MediaSession.Token mMediaSessionToken;
+    private Bundle mExtras;
 
     /**
      * Creates a media browser for the specified media browse service.
@@ -162,7 +169,6 @@ public final class MediaBrowser {
 
     /**
      * Disconnects from the media browse service.
-     * @more
      * After this, no more callbacks will be received.
      */
     public void disconnect() {
@@ -223,7 +229,7 @@ public final class MediaBrowser {
      * </p>
      *
      * @throws IllegalStateException if not connected.
-      */
+     */
     public @NonNull Uri getRoot() {
         if (mState != CONNECT_STATE_CONNECTED) {
             throw new IllegalStateException("getSessionToken() called while not connected (state="
@@ -233,6 +239,19 @@ public final class MediaBrowser {
     }
 
     /**
+     * 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
@@ -332,18 +351,45 @@ public final class MediaBrowser {
     /**
      * 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);
+                    }
+                }
+            }
+        });
     }
 
     /**
@@ -365,7 +411,7 @@ public final class MediaBrowser {
     }
 
     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() {
@@ -382,6 +428,7 @@ public final class MediaBrowser {
                 }
                 mRootUri = root;
                 mMediaSessionToken = session;
+                mExtras = extra;
                 mState = CONNECT_STATE_CONNECTED;
 
                 if (DBG) {
@@ -402,6 +449,17 @@ public final class MediaBrowser {
                     }
                 }
 
+                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);
+                    }
+                }
+
             }
         });
     }
@@ -468,6 +526,31 @@ public final class MediaBrowser {
         });
     }
 
+    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.
@@ -567,6 +650,51 @@ public final class MediaBrowser {
         }
     }
 
+    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.
      */
@@ -666,10 +794,11 @@ public final class MediaBrowser {
          * 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);
             }
         }
 
@@ -691,6 +820,15 @@ public final class MediaBrowser {
                 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 {
index 119f687..38e765f 100644 (file)
@@ -32,6 +32,8 @@ import java.lang.annotation.RetentionPolicy;
  */
 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;
@@ -59,8 +61,8 @@ public final class MediaBrowserItem implements Parcelable {
     /**
      * 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");
         }
@@ -68,6 +70,8 @@ public final class MediaBrowserItem implements Parcelable {
             throw new IllegalArgumentException("title can not be null");
         }
         mUri = uri;
+        mIconUri = iconUri;
+        mIconResId = iconResId;
         mFlags = flags;
         mTitle = title;
         mSummary = summary;
@@ -79,6 +83,8 @@ public final class MediaBrowserItem implements Parcelable {
      */
     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) {
@@ -101,6 +107,8 @@ public final class MediaBrowserItem implements Parcelable {
     @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) {
@@ -138,6 +146,20 @@ public final class MediaBrowserItem implements Parcelable {
     }
 
     /**
+     * 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() {
@@ -195,6 +217,8 @@ public final class MediaBrowserItem implements Parcelable {
         private final Uri mUri;
         private final int mFlags;
         private final CharSequence mTitle;
+        private Uri mIconUri;
+        private int mIconResId;
         private CharSequence mSummary;
         private Bundle mExtras;
 
@@ -214,6 +238,26 @@ public final class MediaBrowserItem implements Parcelable {
         }
 
         /**
+         * 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) {
@@ -234,7 +278,8 @@ public final class MediaBrowserItem implements Parcelable {
         * 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);
         }
     }
 }
index ceb4b03..57befe7 100644 (file)
@@ -25,6 +25,7 @@ import android.app.Service;
 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;
@@ -43,6 +44,7 @@ import java.lang.annotation.RetentionPolicy;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 /**
  * Base class for media browse services.
@@ -81,7 +83,7 @@ public abstract class MediaBrowserService extends Service {
         String pkg;
         Bundle rootHints;
         IMediaBrowserServiceCallbacks callbacks;
-        Uri root;
+        BrowserRoot root;
         HashSet<Uri> subscriptions = new HashSet();
     }
 
@@ -130,7 +132,8 @@ public abstract class MediaBrowserService extends Service {
                         } 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);
@@ -199,6 +202,22 @@ public abstract class MediaBrowserService extends Service {
                 }
             });
         }
+
+        @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
@@ -220,7 +239,7 @@ public abstract class MediaBrowserService extends Service {
     }
 
     /**
-     * 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
@@ -237,8 +256,8 @@ public abstract class MediaBrowserService extends Service {
      * 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.
@@ -247,7 +266,7 @@ public abstract class MediaBrowserService extends Service {
      * 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.
@@ -255,14 +274,11 @@ public abstract class MediaBrowserService extends Service {
      * @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.
@@ -288,15 +304,6 @@ public abstract class MediaBrowserService extends Service {
     }
 
     /**
-     * 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.
@@ -305,7 +312,19 @@ public abstract class MediaBrowserService extends Service {
      * 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;
+                }
+            }
+        }
     }
 
     /**
@@ -360,4 +379,25 @@ public abstract class MediaBrowserService extends Service {
                     + " 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;
+        }
+    }
 }
index 9ca156f..e5216b5 100644 (file)
@@ -31,6 +31,7 @@ import android.media.MediaPlayer.OnErrorListener;
 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;
@@ -115,24 +116,27 @@ public class BrowserService extends MediaBrowserService {
     }
 
     @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;
     }