summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/current.txt8
-rw-r--r--api/system-current.txt8
-rw-r--r--api/test-current.txt8
-rw-r--r--media/java/android/media/browse/MediaBrowser.java332
-rw-r--r--media/java/android/media/browse/MediaBrowserUtils.java72
-rw-r--r--media/java/android/service/media/IMediaBrowserService.aidl11
-rw-r--r--media/java/android/service/media/IMediaBrowserServiceCallbacks.aidl2
-rw-r--r--media/java/android/service/media/MediaBrowserService.java175
8 files changed, 521 insertions, 95 deletions
diff --git a/api/current.txt b/api/current.txt
index 737670baca94..47a635062807 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -21746,7 +21746,11 @@ package android.media.browse {
method public android.media.session.MediaSession.Token getSessionToken();
method public boolean isConnected();
method public void subscribe(java.lang.String, android.media.browse.MediaBrowser.SubscriptionCallback);
+ method public void subscribe(java.lang.String, android.os.Bundle, android.media.browse.MediaBrowser.SubscriptionCallback);
method public void unsubscribe(java.lang.String);
+ method public void unsubscribe(java.lang.String, android.os.Bundle);
+ field public static final java.lang.String EXTRA_PAGE = "android.media.browse.extra.PAGE";
+ field public static final java.lang.String EXTRA_PAGE_SIZE = "android.media.browse.extra.PAGE_SIZE";
}
public static class MediaBrowser.ConnectionCallback {
@@ -21779,7 +21783,9 @@ package android.media.browse {
public static abstract class MediaBrowser.SubscriptionCallback {
ctor public MediaBrowser.SubscriptionCallback();
method public void onChildrenLoaded(java.lang.String, java.util.List<android.media.browse.MediaBrowser.MediaItem>);
+ method public void onChildrenLoaded(java.lang.String, java.util.List<android.media.browse.MediaBrowser.MediaItem>, android.os.Bundle);
method public void onError(java.lang.String);
+ method public void onError(java.lang.String, android.os.Bundle);
}
}
@@ -33800,9 +33806,11 @@ package android.service.media {
method public void dump(java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
method public android.media.session.MediaSession.Token getSessionToken();
method public void notifyChildrenChanged(java.lang.String);
+ method public void notifyChildrenChanged(java.lang.String, android.os.Bundle);
method public android.os.IBinder onBind(android.content.Intent);
method public abstract android.service.media.MediaBrowserService.BrowserRoot onGetRoot(java.lang.String, int, android.os.Bundle);
method public abstract void onLoadChildren(java.lang.String, android.service.media.MediaBrowserService.Result<java.util.List<android.media.browse.MediaBrowser.MediaItem>>);
+ method public void onLoadChildren(java.lang.String, android.service.media.MediaBrowserService.Result<java.util.List<android.media.browse.MediaBrowser.MediaItem>>, android.os.Bundle);
method public void onLoadItem(java.lang.String, android.service.media.MediaBrowserService.Result<android.media.browse.MediaBrowser.MediaItem>);
method public void setSessionToken(android.media.session.MediaSession.Token);
field public static final java.lang.String SERVICE_INTERFACE = "android.media.browse.MediaBrowserService";
diff --git a/api/system-current.txt b/api/system-current.txt
index 3c39edb202b2..30c464a2f2d9 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -23163,7 +23163,11 @@ package android.media.browse {
method public android.media.session.MediaSession.Token getSessionToken();
method public boolean isConnected();
method public void subscribe(java.lang.String, android.media.browse.MediaBrowser.SubscriptionCallback);
+ method public void subscribe(java.lang.String, android.os.Bundle, android.media.browse.MediaBrowser.SubscriptionCallback);
method public void unsubscribe(java.lang.String);
+ method public void unsubscribe(java.lang.String, android.os.Bundle);
+ field public static final java.lang.String EXTRA_PAGE = "android.media.browse.extra.PAGE";
+ field public static final java.lang.String EXTRA_PAGE_SIZE = "android.media.browse.extra.PAGE_SIZE";
}
public static class MediaBrowser.ConnectionCallback {
@@ -23196,7 +23200,9 @@ package android.media.browse {
public static abstract class MediaBrowser.SubscriptionCallback {
ctor public MediaBrowser.SubscriptionCallback();
method public void onChildrenLoaded(java.lang.String, java.util.List<android.media.browse.MediaBrowser.MediaItem>);
+ method public void onChildrenLoaded(java.lang.String, java.util.List<android.media.browse.MediaBrowser.MediaItem>, android.os.Bundle);
method public void onError(java.lang.String);
+ method public void onError(java.lang.String, android.os.Bundle);
}
}
@@ -36022,9 +36028,11 @@ package android.service.media {
method public void dump(java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
method public android.media.session.MediaSession.Token getSessionToken();
method public void notifyChildrenChanged(java.lang.String);
+ method public void notifyChildrenChanged(java.lang.String, android.os.Bundle);
method public android.os.IBinder onBind(android.content.Intent);
method public abstract android.service.media.MediaBrowserService.BrowserRoot onGetRoot(java.lang.String, int, android.os.Bundle);
method public abstract void onLoadChildren(java.lang.String, android.service.media.MediaBrowserService.Result<java.util.List<android.media.browse.MediaBrowser.MediaItem>>);
+ method public void onLoadChildren(java.lang.String, android.service.media.MediaBrowserService.Result<java.util.List<android.media.browse.MediaBrowser.MediaItem>>, android.os.Bundle);
method public void onLoadItem(java.lang.String, android.service.media.MediaBrowserService.Result<android.media.browse.MediaBrowser.MediaItem>);
method public void setSessionToken(android.media.session.MediaSession.Token);
field public static final java.lang.String SERVICE_INTERFACE = "android.media.browse.MediaBrowserService";
diff --git a/api/test-current.txt b/api/test-current.txt
index 52c5b5a8f90c..fab1f045f529 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -21754,7 +21754,11 @@ package android.media.browse {
method public android.media.session.MediaSession.Token getSessionToken();
method public boolean isConnected();
method public void subscribe(java.lang.String, android.media.browse.MediaBrowser.SubscriptionCallback);
+ method public void subscribe(java.lang.String, android.os.Bundle, android.media.browse.MediaBrowser.SubscriptionCallback);
method public void unsubscribe(java.lang.String);
+ method public void unsubscribe(java.lang.String, android.os.Bundle);
+ field public static final java.lang.String EXTRA_PAGE = "android.media.browse.extra.PAGE";
+ field public static final java.lang.String EXTRA_PAGE_SIZE = "android.media.browse.extra.PAGE_SIZE";
}
public static class MediaBrowser.ConnectionCallback {
@@ -21787,7 +21791,9 @@ package android.media.browse {
public static abstract class MediaBrowser.SubscriptionCallback {
ctor public MediaBrowser.SubscriptionCallback();
method public void onChildrenLoaded(java.lang.String, java.util.List<android.media.browse.MediaBrowser.MediaItem>);
+ method public void onChildrenLoaded(java.lang.String, java.util.List<android.media.browse.MediaBrowser.MediaItem>, android.os.Bundle);
method public void onError(java.lang.String);
+ method public void onError(java.lang.String, android.os.Bundle);
}
}
@@ -33814,9 +33820,11 @@ package android.service.media {
method public void dump(java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
method public android.media.session.MediaSession.Token getSessionToken();
method public void notifyChildrenChanged(java.lang.String);
+ method public void notifyChildrenChanged(java.lang.String, android.os.Bundle);
method public android.os.IBinder onBind(android.content.Intent);
method public abstract android.service.media.MediaBrowserService.BrowserRoot onGetRoot(java.lang.String, int, android.os.Bundle);
method public abstract void onLoadChildren(java.lang.String, android.service.media.MediaBrowserService.Result<java.util.List<android.media.browse.MediaBrowser.MediaItem>>);
+ method public void onLoadChildren(java.lang.String, android.service.media.MediaBrowserService.Result<java.util.List<android.media.browse.MediaBrowser.MediaItem>>, android.os.Bundle);
method public void onLoadItem(java.lang.String, android.service.media.MediaBrowserService.Result<android.media.browse.MediaBrowser.MediaItem>);
method public void setSessionToken(android.media.session.MediaSession.Token);
field public static final java.lang.String SERVICE_INTERFACE = "android.media.browse.MediaBrowserService";
diff --git a/media/java/android/media/browse/MediaBrowser.java b/media/java/android/media/browse/MediaBrowser.java
index 4ca89a8b6ec8..869512dd254c 100644
--- a/media/java/android/media/browse/MediaBrowser.java
+++ b/media/java/android/media/browse/MediaBrowser.java
@@ -34,9 +34,9 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
import android.os.ResultReceiver;
-import android.service.media.MediaBrowserService;
import android.service.media.IMediaBrowserService;
import android.service.media.IMediaBrowserServiceCallbacks;
+import android.service.media.MediaBrowserService;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
@@ -44,7 +44,9 @@ import android.util.Log;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
+import java.util.ArrayList;
import java.util.List;
+import java.util.Map.Entry;
/**
* Browses media content offered by a link MediaBrowserService.
@@ -52,11 +54,39 @@ import java.util.List;
* This object is not thread-safe. All calls should happen on the thread on which the browser
* was constructed.
* </p>
+ * <h3>Standard Extra Data</h3>
+ *
+ * <p>These are the current standard fields that can be used as extra data via
+ * {@link #subscribe(String, Bundle, SubscriptionCallback)}, {@link #unsubscribe(String, Bundle)},
+ * and {@link SubscriptionCallback#onChildrenLoaded(String, List, Bundle)}.
+ *
+ * <ul>
+ * <li> {@link #EXTRA_PAGE}
+ * <li> {@link #EXTRA_PAGE_SIZE}
+ * </ul>
*/
public final class MediaBrowser {
private static final String TAG = "MediaBrowser";
private static final boolean DBG = false;
+ /**
+ * Used as an int extra field to denote the page number to subscribe.
+ * The value of {@code EXTRA_PAGE} should be greater than or equal to 1.
+ *
+ * @see android.service.media.MediaBrowserService.BrowserRoot
+ * @see #EXTRA_PAGE_SIZE
+ */
+ public static final String EXTRA_PAGE = "android.media.browse.extra.PAGE";
+
+ /**
+ * Used as an int extra field to denote the number of media items in a page.
+ * The value of {@code EXTRA_PAGE_SIZE} should be greater than or equal to 1.
+ *
+ * @see android.service.media.MediaBrowserService.BrowserRoot
+ * @see #EXTRA_PAGE
+ */
+ public static final String EXTRA_PAGE_SIZE = "android.media.browse.extra.PAGE_SIZE";
+
private static final int CONNECT_STATE_DISCONNECTED = 0;
private static final int CONNECT_STATE_CONNECTING = 1;
private static final int CONNECT_STATE_CONNECTED = 2;
@@ -67,8 +97,7 @@ public final class MediaBrowser {
private final ConnectionCallback mCallback;
private final Bundle mRootHints;
private final Handler mHandler = new Handler();
- private final ArrayMap<String,Subscription> mSubscriptions =
- new ArrayMap<String, MediaBrowser.Subscription>();
+ private final ArrayMap<String, Subscription> mSubscriptions = new ArrayMap<>();
private int mState = CONNECT_STATE_DISCONNECTED;
private MediaServiceConnection mServiceConnection;
@@ -291,7 +320,7 @@ public final class MediaBrowser {
* the specified id and subscribes to receive updates when they change.
* <p>
* The list of subscriptions is maintained even when not connected and is
- * restored after reconnection. It is ok to subscribe while not connected
+ * restored after the reconnection. It is ok to subscribe while not connected
* but the results will not be returned until the connection completes.
* </p>
* <p>
@@ -305,34 +334,37 @@ public final class MediaBrowser {
* @param callback The callback to receive the list of children.
*/
public void subscribe(@NonNull String parentId, @NonNull SubscriptionCallback callback) {
- // 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;
+ subscribeInternal(parentId, null, 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) {
- 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);
- }
+ /**
+ * Queries with service-specific arguments for information about the media items
+ * that are contained within the specified id and subscribes to receive updates
+ * when they change.
+ * <p>
+ * The list of subscriptions is maintained even when not connected and is
+ * restored after the reconnection. It is ok to subscribe while not connected
+ * but the results will not be returned until the connection completes.
+ * </p>
+ * <p>
+ * If the id is already subscribed with a different callback then the new
+ * callback will replace the previous one and the child data will be
+ * reloaded.
+ * </p>
+ *
+ * @param parentId The id of the parent media item whose list of children
+ * will be subscribed.
+ * @param options A bundle of service-specific arguments to send to the media
+ * browse service. The contents of this bundle may affect the
+ * information returned when browsing.
+ * @param callback The callback to receive the list of children.
+ */
+ public void subscribe(@NonNull String parentId, @NonNull Bundle options,
+ @NonNull SubscriptionCallback callback) {
+ if (options == null) {
+ throw new IllegalArgumentException("options are null");
}
+ subscribeInternal(parentId, options, callback);
}
/**
@@ -343,27 +375,28 @@ public final class MediaBrowser {
* </p>
*
* @param parentId The id of the parent media item whose list of children
- * will be unsubscribed.
+ * will be unsubscribed.
*/
public void unsubscribe(@NonNull String parentId) {
- // Check arguments.
- if (TextUtils.isEmpty(parentId)) {
- throw new IllegalArgumentException("parentId is empty.");
- }
-
- // Remove from our list.
- final Subscription sub = mSubscriptions.remove(parentId);
+ unsubscribeInternal(parentId, null);
+ }
- // 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);
- }
+ /**
+ * Unsubscribes for changes to the children of the specified media id.
+ * <p>
+ * The query callback will no longer be invoked for results associated with
+ * this id once this method returns.
+ * </p>
+ *
+ * @param parentId The id of the parent media item whose list of children
+ * will be unsubscribed.
+ * @param options A bundle sent to the media browse service to subscribe.
+ */
+ public void unsubscribe(@NonNull String parentId, @NonNull Bundle options) {
+ if (options == null) {
+ throw new IllegalArgumentException("options are null");
}
+ unsubscribeInternal(parentId, options);
}
/**
@@ -420,6 +453,73 @@ public final class MediaBrowser {
}
}
+ private void subscribeInternal(String parentId, Bundle options, SubscriptionCallback callback) {
+ // 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);
+ if (sub == null) {
+ sub = new Subscription();
+ mSubscriptions.put(parentId, sub);
+ }
+ sub.add(callback, options);
+
+ // 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) {
+ try {
+ // NOTE: In order not to break the behavior of the support library, call
+ // addSubscription instead of addSubscriptionWithOptions when the options are null.
+ if (options == null) {
+ mServiceBinder.addSubscription(parentId, mServiceCallbacks);
+ } else {
+ mServiceBinder.addSubscriptionWithOptions(parentId, options, 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);
+ }
+ }
+ }
+
+ private void unsubscribeInternal(String parentId, Bundle options) {
+ // Check arguments.
+ if (TextUtils.isEmpty(parentId)) {
+ throw new IllegalArgumentException("parentId is empty.");
+ }
+
+ // Remove from our list.
+ Subscription sub = mSubscriptions.get(parentId);
+
+ // Tell the service if necessary.
+ if (sub != null && sub.remove(options) && mState == CONNECT_STATE_CONNECTED) {
+ try {
+ // NOTE: In order not to break the behavior of the support library, call
+ // removeSubscription instead of removeSubscriptionWithOptions when the options
+ // are null.
+ if (options == null) {
+ mServiceBinder.removeSubscription(parentId, mServiceCallbacks);
+ } else {
+ mServiceBinder.removeSubscriptionWithOptions(
+ parentId, options, 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);
+ }
+ }
+ if (sub != null && sub.isEmpty()) {
+ mSubscriptions.remove(parentId);
+ }
+ }
+
/**
* For debugging.
*/
@@ -467,13 +567,26 @@ public final class MediaBrowser {
// 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);
+ for (Entry<String, Subscription> subscriptionEntry : mSubscriptions.entrySet()) {
+ String id = subscriptionEntry.getKey();
+ Subscription sub = subscriptionEntry.getValue();
+ for (Bundle options : sub.getOptionsList()) {
+ try {
+ // NOTE: In order not to break the behavior of the support library,
+ // call addSubscription instead of addSubscriptionWithOptions when
+ // the options are null.
+ if (options == null) {
+ mServiceBinder.addSubscription(id, mServiceCallbacks);
+ } else {
+ mServiceBinder.addSubscriptionWithOptions(
+ id, options, 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);
+ }
}
}
}
@@ -508,7 +621,7 @@ public final class MediaBrowser {
}
private final void onLoadChildren(final IMediaBrowserServiceCallbacks callback,
- final String parentId, final ParceledListSlice list) {
+ final String parentId, final ParceledListSlice list, final Bundle options) {
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -525,16 +638,21 @@ public final class MediaBrowser {
// 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);
+ if (subscription != null) {
+ // Tell the app.
+ SubscriptionCallback subscriptionCallback = subscription.getCallback(options);
+ if (subscriptionCallback != null) {
+ if (options == null) {
+ subscriptionCallback.onChildrenLoaded(parentId, data);
+ } else {
+ subscriptionCallback.onChildrenLoaded(parentId, data, options);
+ }
+ return;
}
- return;
}
-
- // Tell the app.
- subscription.callback.onChildrenLoaded(parentId, data);
+ if (DBG) {
+ Log.d(TAG, "onLoadChildren for id that isn't subscribed id=" + parentId);
+ }
}
});
}
@@ -697,7 +815,6 @@ public final class MediaBrowser {
}
}
-
/**
* Callbacks for connection related events.
*/
@@ -735,6 +852,19 @@ public final class MediaBrowser {
}
/**
+ * Called when the list of children is loaded or updated.
+ *
+ * @param parentId The media id of the parent media item.
+ * @param children The children which were loaded, or null if the id is invalid.
+ * @param options A bundle of service-specific arguments to send to the media
+ * browse service. The contents of this bundle may affect the
+ * information returned when browsing.
+ */
+ public void onChildrenLoaded(@NonNull String parentId, List<MediaItem> children,
+ @NonNull Bundle options) {
+ }
+
+ /**
* Called when the id doesn't exist or other errors in subscribing.
* <p>
* If this is called, the subscription remains until {@link MediaBrowser#unsubscribe}
@@ -742,10 +872,25 @@ public final class MediaBrowser {
* </p>
*
* @param parentId The media id of the parent media item whose children could
- * not be loaded.
+ * not be loaded.
*/
public void onError(@NonNull String parentId) {
}
+
+ /**
+ * Called when the id doesn't exist or other errors in subscribing.
+ * <p>
+ * If this is called, the subscription remains until {@link MediaBrowser#unsubscribe}
+ * called, because some errors may heal themselves.
+ * </p>
+ *
+ * @param parentId The media id of the parent media item whose children could
+ * not be loaded.
+ * @param options A bundle of service-specific arguments sent to the media
+ * browse service.
+ */
+ public void onError(@NonNull String parentId, @NonNull Bundle options) {
+ }
}
/**
@@ -909,20 +1054,65 @@ public final class MediaBrowser {
}
@Override
- public void onLoadChildren(final String parentId, final ParceledListSlice list) {
+ public void onLoadChildren(final String parentId, final ParceledListSlice list,
+ final Bundle options) {
MediaBrowser mediaBrowser = mMediaBrowser.get();
if (mediaBrowser != null) {
- mediaBrowser.onLoadChildren(this, parentId, list);
+ mediaBrowser.onLoadChildren(this, parentId, list, options);
}
}
}
private static class Subscription {
- final String id;
- SubscriptionCallback callback;
+ private final List<SubscriptionCallback> mCallbacks;
+ private final List<Bundle> mOptionsList;
- Subscription(String id) {
- this.id = id;
+ public Subscription() {
+ mCallbacks = new ArrayList<>();
+ mOptionsList = new ArrayList<>();
+ }
+
+ public boolean isEmpty() {
+ return mCallbacks.isEmpty();
+ }
+
+ public List<Bundle> getOptionsList() {
+ return mOptionsList;
+ }
+
+ public List<SubscriptionCallback> getCallbacks() {
+ return mCallbacks;
+ }
+
+ public void add(SubscriptionCallback callback, Bundle options) {
+ for (int i = 0; i < mOptionsList.size(); ++i) {
+ if (MediaBrowserUtils.areSameOptions(mOptionsList.get(i), options)) {
+ mCallbacks.set(i, callback);
+ return;
+ }
+ }
+ mCallbacks.add(callback);
+ mOptionsList.add(options);
+ }
+
+ public boolean remove(Bundle options) {
+ for (int i = 0; i < mOptionsList.size(); ++i) {
+ if (MediaBrowserUtils.areSameOptions(mOptionsList.get(i), options)) {
+ mCallbacks.remove(i);
+ mOptionsList.remove(i);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public SubscriptionCallback getCallback(Bundle options) {
+ for (int i = 0; i < mOptionsList.size(); ++i) {
+ if (MediaBrowserUtils.areSameOptions(mOptionsList.get(i), options)) {
+ return mCallbacks.get(i);
+ }
+ }
+ return null;
}
}
}
diff --git a/media/java/android/media/browse/MediaBrowserUtils.java b/media/java/android/media/browse/MediaBrowserUtils.java
new file mode 100644
index 000000000000..4f198aca96ab
--- /dev/null
+++ b/media/java/android/media/browse/MediaBrowserUtils.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.browse;
+
+import android.os.Bundle;
+
+/**
+ * @hide
+ */
+public class MediaBrowserUtils {
+ public static boolean areSameOptions(Bundle options1, Bundle options2) {
+ if (options1 == options2) {
+ return true;
+ } else if (options1 == null) {
+ return options2.getInt(MediaBrowser.EXTRA_PAGE, -1) == -1
+ && options2.getInt(MediaBrowser.EXTRA_PAGE_SIZE, -1) == -1;
+ } else if (options2 == null) {
+ return options1.getInt(MediaBrowser.EXTRA_PAGE, -1) == -1
+ && options1.getInt(MediaBrowser.EXTRA_PAGE_SIZE, -1) == -1;
+ } else {
+ return options1.getInt(MediaBrowser.EXTRA_PAGE, -1)
+ == options2.getInt(MediaBrowser.EXTRA_PAGE, -1)
+ && options1.getInt(MediaBrowser.EXTRA_PAGE_SIZE, -1)
+ == options2.getInt(MediaBrowser.EXTRA_PAGE_SIZE, -1);
+ }
+ }
+
+ public static boolean hasDuplicatedItems(Bundle options1, Bundle options2) {
+ int page1 = options1.getInt(MediaBrowser.EXTRA_PAGE, -1);
+ int page2 = options2.getInt(MediaBrowser.EXTRA_PAGE, -1);
+ int pageSize1 = options1.getInt(MediaBrowser.EXTRA_PAGE_SIZE, -1);
+ int pageSize2 = options2.getInt(MediaBrowser.EXTRA_PAGE_SIZE, -1);
+
+ int startIndex1, startIndex2, endIndex1, endIndex2;
+ if (page1 == -1 || pageSize1 == -1) {
+ startIndex1 = 0;
+ endIndex1 = Integer.MAX_VALUE;
+ } else {
+ startIndex1 = pageSize1 * (page1 - 1);
+ endIndex1 = startIndex1 + pageSize1 - 1;
+ }
+
+ if (page2 == -1 || pageSize2 == -1) {
+ startIndex2 = 0;
+ endIndex2 = Integer.MAX_VALUE;
+ } else {
+ startIndex2 = pageSize2 * (page2 - 1);
+ endIndex2 = startIndex2 + pageSize2 - 1;
+ }
+
+ if (startIndex1 <= startIndex2 && startIndex2 <= endIndex1) {
+ return true;
+ } else if (startIndex1 <= endIndex2 && endIndex2 <= endIndex1) {
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/media/java/android/service/media/IMediaBrowserService.aidl b/media/java/android/service/media/IMediaBrowserService.aidl
index f01fc076badf..fe7ebfa95423 100644
--- a/media/java/android/service/media/IMediaBrowserService.aidl
+++ b/media/java/android/service/media/IMediaBrowserService.aidl
@@ -14,10 +14,19 @@ import android.os.ResultReceiver;
* @hide
*/
oneway interface IMediaBrowserService {
+
+ // Warning: DO NOT CHANGE the methods signature and order of methods.
+ // The change of the order or the method signatures could break the support library.
+
void connect(String pkg, in Bundle rootHints, IMediaBrowserServiceCallbacks callbacks);
void disconnect(IMediaBrowserServiceCallbacks callbacks);
void addSubscription(String uri, IMediaBrowserServiceCallbacks callbacks);
void removeSubscription(String uri, IMediaBrowserServiceCallbacks callbacks);
void getMediaItem(String uri, in ResultReceiver cb);
-} \ No newline at end of file
+
+ void addSubscriptionWithOptions(String uri, in Bundle options,
+ IMediaBrowserServiceCallbacks callbacks);
+ void removeSubscriptionWithOptions(String uri, in Bundle options,
+ IMediaBrowserServiceCallbacks callbacks);
+}
diff --git a/media/java/android/service/media/IMediaBrowserServiceCallbacks.aidl b/media/java/android/service/media/IMediaBrowserServiceCallbacks.aidl
index 2a37ada470ff..dadb025060b7 100644
--- a/media/java/android/service/media/IMediaBrowserServiceCallbacks.aidl
+++ b/media/java/android/service/media/IMediaBrowserServiceCallbacks.aidl
@@ -22,5 +22,5 @@ oneway interface IMediaBrowserServiceCallbacks {
*/
void onConnect(String root, in MediaSession.Token session, in Bundle extras);
void onConnectFailed();
- void onLoadChildren(String mediaId, in ParceledListSlice list);
+ void onLoadChildren(String mediaId, in ParceledListSlice list, in Bundle options);
}
diff --git a/media/java/android/service/media/MediaBrowserService.java b/media/java/android/service/media/MediaBrowserService.java
index 8edccacfda09..6cf90d594dce 100644
--- a/media/java/android/service/media/MediaBrowserService.java
+++ b/media/java/android/service/media/MediaBrowserService.java
@@ -25,11 +25,12 @@ import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.media.browse.MediaBrowser;
+import android.media.browse.MediaBrowserUtils;
import android.media.session.MediaSession;
import android.os.Binder;
import android.os.Bundle;
-import android.os.IBinder;
import android.os.Handler;
+import android.os.IBinder;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.service.media.IMediaBrowserService;
@@ -40,7 +41,8 @@ import android.util.Log;
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.util.HashSet;
+import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
/**
@@ -82,7 +84,9 @@ public abstract class MediaBrowserService extends Service {
*/
public static final String KEY_MEDIA_ITEM = "media_item";
- private final ArrayMap<IBinder, ConnectionRecord> mConnections = new ArrayMap();
+ private static final int RESULT_FLAG_OPTION_NOT_HANDLED = 0x00000001;
+
+ private final ArrayMap<IBinder, ConnectionRecord> mConnections = new ArrayMap<>();
private final Handler mHandler = new Handler();
private ServiceBinder mBinder;
MediaSession.Token mSession;
@@ -95,7 +99,7 @@ public abstract class MediaBrowserService extends Service {
Bundle rootHints;
IMediaBrowserServiceCallbacks callbacks;
BrowserRoot root;
- HashSet<String> subscriptions = new HashSet();
+ HashMap<String, List<Bundle>> subscriptions = new HashMap<>();
}
/**
@@ -115,6 +119,7 @@ public abstract class MediaBrowserService extends Service {
private Object mDebug;
private boolean mDetachCalled;
private boolean mSendResultCalled;
+ private int mFlag;
Result(Object debug) {
mDebug = debug;
@@ -128,7 +133,7 @@ public abstract class MediaBrowserService extends Service {
throw new IllegalStateException("sendResult() called twice for: " + mDebug);
}
mSendResultCalled = true;
- onResultSent(result);
+ onResultSent(result, mFlag);
}
/**
@@ -151,11 +156,15 @@ public abstract class MediaBrowserService extends Service {
return mDetachCalled || mSendResultCalled;
}
+ void setFlag(int flag) {
+ mFlag = flag;
+ }
+
/**
* Called when the result is sent, after assertions about not being called twice
* have happened.
*/
- void onResultSent(T result) {
+ void onResultSent(T result, int flag) {
}
}
@@ -228,9 +237,15 @@ public abstract class MediaBrowserService extends Service {
});
}
+ @Override
+ public void addSubscription(final String id,
+ final IMediaBrowserServiceCallbacks callbacks) {
+ addSubscriptionWithOptions(id, null, callbacks);
+ }
@Override
- public void addSubscription(final String id, final IMediaBrowserServiceCallbacks callbacks) {
+ public void addSubscriptionWithOptions(final String id, final Bundle options,
+ final IMediaBrowserServiceCallbacks callbacks) {
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -244,7 +259,7 @@ public abstract class MediaBrowserService extends Service {
return;
}
- MediaBrowserService.this.addSubscription(id, connection);
+ MediaBrowserService.this.addSubscription(id, connection, options);
}
});
}
@@ -252,6 +267,12 @@ public abstract class MediaBrowserService extends Service {
@Override
public void removeSubscription(final String id,
final IMediaBrowserServiceCallbacks callbacks) {
+ removeSubscriptionWithOptions(id, null, callbacks);
+ }
+
+ @Override
+ public void removeSubscriptionWithOptions(final String id, final Bundle options,
+ final IMediaBrowserServiceCallbacks callbacks) {
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -263,7 +284,7 @@ public abstract class MediaBrowserService extends Service {
+ id);
return;
}
- if (!connection.subscriptions.remove(id)) {
+ if (!MediaBrowserService.this.removeSubscription(id, connection, options)) {
Log.w(TAG, "removeSubscription called for " + id
+ " which is not subscribed");
}
@@ -345,6 +366,33 @@ public abstract class MediaBrowserService extends Service {
@NonNull Result<List<MediaBrowser.MediaItem>> result);
/**
+ * Called to get information about the children of a media item.
+ * <p>
+ * Implementations must call {@link Result#sendResult result.sendResult}
+ * with the list of children. If loading the children will be an expensive
+ * operation that should be performed on another thread,
+ * {@link Result#detach result.detach} may be called before returning from
+ * this function, and then {@link Result#sendResult result.sendResult}
+ * called when the loading is complete.
+ *
+ * @param parentId The id of the parent media item whose children are to be
+ * queried.
+ * @param result The Result to send the list of children to, or null if the
+ * id is invalid.
+ * @param options A bundle of service-specific arguments sent from the media
+ * browse. The information returned through the result should be
+ * affected by the contents of this bundle.
+ */
+ public void onLoadChildren(@NonNull String parentId,
+ @NonNull Result<List<MediaBrowser.MediaItem>> result, @NonNull Bundle options) {
+ // To support backward compatibility, when the implementation of MediaBrowserService doesn't
+ // override onLoadChildren() with options, onLoadChildren() without options will be used
+ // instead, and the options will be applied in the implementation of result.onResultSent().
+ result.setFlag(RESULT_FLAG_OPTION_NOT_HANDLED);
+ onLoadChildren(parentId, result);
+ }
+
+ /**
* Called to get information about a specific media item.
* <p>
* Implementations must call {@link Result#sendResult result.sendResult}. If
@@ -413,7 +461,29 @@ public abstract class MediaBrowserService extends Service {
* @param parentId The id of the parent media item whose
* children changed.
*/
- public void notifyChildrenChanged(@NonNull final String parentId) {
+ public void notifyChildrenChanged(@NonNull String parentId) {
+ notifyChildrenChangedInternal(parentId, null);
+ }
+
+ /**
+ * Notifies all connected media browsers that the children of
+ * the specified parent id have changed in some way.
+ * This will cause browsers to fetch subscribed content again.
+ *
+ * @param parentId The id of the parent media item whose
+ * children changed.
+ * @param options A bundle of service-specific arguments to send
+ * to the media browse. The contents of this bundle may
+ * contain the information about the change.
+ */
+ public void notifyChildrenChanged(@NonNull String parentId, @NonNull Bundle options) {
+ if (options == null) {
+ throw new IllegalArgumentException("options cannot be null in notifyChildrenChanged");
+ }
+ notifyChildrenChangedInternal(parentId, options);
+ }
+
+ private void notifyChildrenChangedInternal(final String parentId, final Bundle options) {
if (parentId == null) {
throw new IllegalArgumentException("parentId cannot be null in notifyChildrenChanged");
}
@@ -422,8 +492,13 @@ public abstract class MediaBrowserService extends Service {
public void run() {
for (IBinder binder : mConnections.keySet()) {
ConnectionRecord connection = mConnections.get(binder);
- if (connection.subscriptions.contains(parentId)) {
- performLoadChildren(parentId, connection);
+ List<Bundle> optionsList = connection.subscriptions.get(parentId);
+ if (optionsList != null) {
+ for (Bundle bundle : optionsList) {
+ if (MediaBrowserUtils.hasDuplicatedItems(options, bundle)) {
+ performLoadChildren(parentId, connection, bundle);
+ }
+ }
}
}
}
@@ -451,12 +526,42 @@ public abstract class MediaBrowserService extends Service {
/**
* Save the subscription and if it is a new subscription send the results.
*/
- private void addSubscription(String id, ConnectionRecord connection) {
+ private void addSubscription(String id, ConnectionRecord connection, Bundle options) {
// Save the subscription
- connection.subscriptions.add(id);
-
+ List<Bundle> optionsList = connection.subscriptions.get(id);
+ if (optionsList == null) {
+ optionsList = new ArrayList<>();
+ }
+ for (Bundle bundle : optionsList) {
+ if (MediaBrowserUtils.areSameOptions(options, bundle)) {
+ return;
+ }
+ }
+ optionsList.add(options);
+ connection.subscriptions.put(id, optionsList);
// send the results
- performLoadChildren(id, connection);
+ performLoadChildren(id, connection, options);
+ }
+
+ /**
+ * Remove the subscription.
+ */
+ private boolean removeSubscription(String id, ConnectionRecord connection, Bundle options) {
+ boolean removed = false;
+ List<Bundle> optionsList = connection.subscriptions.get(id);
+ if (optionsList != null) {
+ for (Bundle bundle : optionsList) {
+ if (MediaBrowserUtils.areSameOptions(options, bundle)) {
+ removed = true;
+ optionsList.remove(bundle);
+ break;
+ }
+ }
+ if (optionsList.size() == 0) {
+ connection.subscriptions.remove(id);
+ }
+ }
+ return removed;
}
/**
@@ -464,11 +569,12 @@ public abstract class MediaBrowserService extends Service {
* <p>
* Callers must make sure that this connection is still connected.
*/
- private void performLoadChildren(final String parentId, final ConnectionRecord connection) {
+ private void performLoadChildren(final String parentId, final ConnectionRecord connection,
+ final Bundle options) {
final Result<List<MediaBrowser.MediaItem>> result
= new Result<List<MediaBrowser.MediaItem>>(parentId) {
@Override
- void onResultSent(List<MediaBrowser.MediaItem> list) {
+ void onResultSent(List<MediaBrowser.MediaItem> list, int flag) {
if (mConnections.get(connection.callbacks.asBinder()) != connection) {
if (DBG) {
Log.d(TAG, "Not sending onLoadChildren result for connection that has"
@@ -477,10 +583,13 @@ public abstract class MediaBrowserService extends Service {
return;
}
+ List<MediaBrowser.MediaItem> filteredList =
+ (flag & RESULT_FLAG_OPTION_NOT_HANDLED) != 0
+ ? applyOptions(list, options) : list;
final ParceledListSlice<MediaBrowser.MediaItem> pls =
- list == null ? null : new ParceledListSlice(list);
+ filteredList == null ? null : new ParceledListSlice<>(filteredList);
try {
- connection.callbacks.onLoadChildren(parentId, pls);
+ connection.callbacks.onLoadChildren(parentId, pls, options);
} catch (RemoteException ex) {
// The other side is in the process of crashing.
Log.w(TAG, "Calling onLoadChildren() failed for id=" + parentId
@@ -489,7 +598,11 @@ public abstract class MediaBrowserService extends Service {
}
};
- onLoadChildren(parentId, result);
+ if (options == null) {
+ onLoadChildren(parentId, result);
+ } else {
+ onLoadChildren(parentId, result, options);
+ }
if (!result.isDone()) {
throw new IllegalStateException("onLoadChildren must call detach() or sendResult()"
@@ -497,11 +610,29 @@ public abstract class MediaBrowserService extends Service {
}
}
+ private List<MediaBrowser.MediaItem> applyOptions(List<MediaBrowser.MediaItem> list,
+ final Bundle options) {
+ int page = options.getInt(MediaBrowser.EXTRA_PAGE, -1);
+ int pageSize = options.getInt(MediaBrowser.EXTRA_PAGE_SIZE, -1);
+ if (page == -1 && pageSize == -1) {
+ return list;
+ }
+ int fromIndex = pageSize * (page - 1);
+ int toIndex = fromIndex + pageSize;
+ if (page < 1 || pageSize < 1 || fromIndex >= list.size()) {
+ return null;
+ }
+ if (toIndex > list.size()) {
+ toIndex = list.size();
+ }
+ return list.subList(fromIndex, toIndex);
+ }
+
private void performLoadItem(String itemId, final ResultReceiver receiver) {
final Result<MediaBrowser.MediaItem> result =
new Result<MediaBrowser.MediaItem>(itemId) {
@Override
- void onResultSent(MediaBrowser.MediaItem item) {
+ void onResultSent(MediaBrowser.MediaItem item, int flag) {
Bundle bundle = new Bundle();
bundle.putParcelable(KEY_MEDIA_ITEM, item);
receiver.send(0, bundle);