MediaBrowser: Introduce the search API

Bug: 28210241
Test: passed cts test (MediaBrowserServiceTest and MediaBrowserTest)
Change-Id: I73c58f5385637c626da1503d27e518b4f0c9ea80
This commit is contained in:
Sungsoo
2016-11-21 16:39:33 +09:00
parent d202a96073
commit 8fb6addcbe
6 changed files with 234 additions and 42 deletions

View File

@@ -22614,6 +22614,7 @@ package android.media.browse {
method public android.content.ComponentName getServiceComponent();
method public android.media.session.MediaSession.Token getSessionToken();
method public boolean isConnected();
method public void search(java.lang.String, android.os.Bundle, android.media.browse.MediaBrowser.SearchCallback);
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);
@@ -22649,6 +22650,12 @@ package android.media.browse {
field public static final int FLAG_PLAYABLE = 2; // 0x2
}
public static abstract class MediaBrowser.SearchCallback {
ctor public MediaBrowser.SearchCallback();
method public void onError(java.lang.String, android.os.Bundle);
method public void onSearchResult(java.lang.String, android.os.Bundle, java.util.List<android.media.browse.MediaBrowser.MediaItem>);
}
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>);
@@ -35074,6 +35081,7 @@ package android.service.media {
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 onSearch(java.lang.String, android.os.Bundle, android.service.media.MediaBrowserService.Result<java.util.List<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";
}

View File

@@ -24257,6 +24257,7 @@ package android.media.browse {
method public android.content.ComponentName getServiceComponent();
method public android.media.session.MediaSession.Token getSessionToken();
method public boolean isConnected();
method public void search(java.lang.String, android.os.Bundle, android.media.browse.MediaBrowser.SearchCallback);
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);
@@ -24292,6 +24293,12 @@ package android.media.browse {
field public static final int FLAG_PLAYABLE = 2; // 0x2
}
public static abstract class MediaBrowser.SearchCallback {
ctor public MediaBrowser.SearchCallback();
method public void onError(java.lang.String, android.os.Bundle);
method public void onSearchResult(java.lang.String, android.os.Bundle, java.util.List<android.media.browse.MediaBrowser.MediaItem>);
}
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>);
@@ -37907,6 +37914,7 @@ package android.service.media {
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 onSearch(java.lang.String, android.os.Bundle, android.service.media.MediaBrowserService.Result<java.util.List<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";
}

View File

@@ -22701,6 +22701,7 @@ package android.media.browse {
method public android.content.ComponentName getServiceComponent();
method public android.media.session.MediaSession.Token getSessionToken();
method public boolean isConnected();
method public void search(java.lang.String, android.os.Bundle, android.media.browse.MediaBrowser.SearchCallback);
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);
@@ -22736,6 +22737,12 @@ package android.media.browse {
field public static final int FLAG_PLAYABLE = 2; // 0x2
}
public static abstract class MediaBrowser.SearchCallback {
ctor public MediaBrowser.SearchCallback();
method public void onError(java.lang.String, android.os.Bundle);
method public void onSearchResult(java.lang.String, android.os.Bundle, java.util.List<android.media.browse.MediaBrowser.MediaItem>);
}
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>);
@@ -35171,6 +35178,7 @@ package android.service.media {
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 onSearch(java.lang.String, android.os.Bundle, android.service.media.MediaBrowserService.Result<java.util.List<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";
}

View File

@@ -109,13 +109,13 @@ public final class MediaBrowser {
private IMediaBrowserServiceCallbacks mServiceCallbacks;
/**
* Creates a media browser for the specified media browse service.
* Creates a media browser for the specified media browser service.
*
* @param context The context.
* @param serviceComponent The component name of the media browse service.
* @param serviceComponent The component name of the media browser 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
* to the media browser 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.
* @see android.service.media.MediaBrowserService.BrowserRoot#EXTRA_RECENT
@@ -140,7 +140,7 @@ public final class MediaBrowser {
}
/**
* Connects to the media browse service.
* Connects to the media browser service.
* <p>
* The connection callback specified in the constructor will be invoked
* when the connection completes or fails.
@@ -206,7 +206,7 @@ public final class MediaBrowser {
}
/**
* Disconnects from the media browse service.
* Disconnects from the media browser service.
* After this, no more callbacks will be received.
*/
public void disconnect() {
@@ -362,7 +362,7 @@ public final class MediaBrowser {
*
* @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
* @param options The 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.
@@ -370,7 +370,7 @@ public final class MediaBrowser {
public void subscribe(@NonNull String parentId, @NonNull Bundle options,
@NonNull SubscriptionCallback callback) {
if (options == null) {
throw new IllegalArgumentException("options are null");
throw new IllegalArgumentException("options cannot be null");
}
subscribeInternal(parentId, new Bundle(options), callback);
}
@@ -398,11 +398,11 @@ public final class MediaBrowser {
*
* @param parentId The id of the parent media item whose list of children
* will be unsubscribed.
* @param callback A callback sent to the media browse service to subscribe.
* @param callback A callback sent to the media browser service to subscribe.
*/
public void unsubscribe(@NonNull String parentId, @NonNull SubscriptionCallback callback) {
if (callback == null) {
throw new IllegalArgumentException("callback is null");
throw new IllegalArgumentException("callback cannot be null");
}
unsubscribeInternal(parentId, callback);
}
@@ -417,10 +417,10 @@ public final class MediaBrowser {
*/
public void getItem(final @NonNull String mediaId, @NonNull final ItemCallback cb) {
if (TextUtils.isEmpty(mediaId)) {
throw new IllegalArgumentException("mediaId is empty.");
throw new IllegalArgumentException("mediaId cannot be empty.");
}
if (cb == null) {
throw new IllegalArgumentException("cb is null.");
throw new IllegalArgumentException("cb cannot be null.");
}
if (mState != CONNECT_STATE_CONNECTED) {
Log.i(TAG, "Not connected, unable to retrieve the MediaItem.");
@@ -451,7 +451,7 @@ public final class MediaBrowser {
try {
mServiceBinder.getMediaItem(mediaId, receiver, mServiceCallbacks);
} catch (RemoteException e) {
Log.i(TAG, "Remote error getting media item.");
Log.i(TAG, "Remote error getting media item.", e);
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -461,13 +461,74 @@ public final class MediaBrowser {
}
}
/**
* Searches {@link MediaItem media items} from the connected service. Not
* all services may support this, and {@link SearchCallback#onError} will be
* called if not implemented.
*
* @param query The search query that contains keywords separated by space. Should not be
* an empty string.
* @param extras The bundle of service-specific arguments to send to the media browser
* service. The contents of this bundle may affect the search result.
* @param callback The callback to receive the search result.
*/
public void search(@NonNull final String query, final Bundle extras, SearchCallback callback) {
if (TextUtils.isEmpty(query)) {
throw new IllegalArgumentException("query cannot be empty.");
}
if (callback == null) {
throw new IllegalArgumentException("callback cannot be null.");
}
if (mState != CONNECT_STATE_CONNECTED) {
Log.i(TAG, "Not connected, unable to search.");
mHandler.post(new Runnable() {
@Override
public void run() {
callback.onError(query, extras);
}
});
return;
}
ResultReceiver receiver = new ResultReceiver(mHandler) {
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
if (resultCode != 0 || resultData == null
|| !resultData.containsKey(MediaBrowserService.KEY_SEARCH_RESULTS)) {
callback.onError(query, extras);
return;
}
Parcelable[] items = resultData.getParcelableArray(
MediaBrowserService.KEY_SEARCH_RESULTS);
List<MediaItem> results = null;
if (items != null) {
results = new ArrayList<>();
for (Parcelable item : items) {
results.add((MediaItem) item);
}
}
callback.onSearchResult(query, extras, results);
}
};
try {
mServiceBinder.search(query, extras, receiver, mServiceCallbacks);
} catch (RemoteException e) {
Log.i(TAG, "Remote error getting media item.", e);
mHandler.post(new Runnable() {
@Override
public void run() {
callback.onError(query, extras);
}
});
}
}
private void subscribeInternal(String parentId, Bundle options, SubscriptionCallback callback) {
// Check arguments.
if (TextUtils.isEmpty(parentId)) {
throw new IllegalArgumentException("parentId is empty.");
throw new IllegalArgumentException("parentId cannot be empty.");
}
if (callback == null) {
throw new IllegalArgumentException("callback is null");
throw new IllegalArgumentException("callback cannot be null");
}
// Update or create the subscription.
Subscription sub = mSubscriptions.get(parentId);
@@ -497,7 +558,7 @@ public final class MediaBrowser {
private void unsubscribeInternal(String parentId, SubscriptionCallback callback) {
// Check arguments.
if (TextUtils.isEmpty(parentId)) {
throw new IllegalArgumentException("parentId is empty.");
throw new IllegalArgumentException("parentId cannot be empty.");
}
Subscription sub = mSubscriptions.get(parentId);
@@ -712,7 +773,9 @@ public final class MediaBrowser {
}
/**
* A class with information on a single media item for use in browsing media.
* A class with information on a single media item for use in browsing/searching media.
* MediaItems are application dependent so we cannot guarantee that they contain the
* right values.
*/
public static class MediaItem implements Parcelable {
private final int mFlags;
@@ -830,7 +893,7 @@ public final class MediaBrowser {
* Returns the media id in the {@link MediaDescription} for this item.
* @see android.media.MediaMetadata#METADATA_KEY_MEDIA_ID
*/
public @NonNull String getMediaId() {
public @Nullable String getMediaId() {
return mDescription.getMediaId();
}
}
@@ -882,7 +945,7 @@ public final class MediaBrowser {
*
* @param parentId The media id of the parent media item.
* @param children The children which were loaded.
* @param options A bundle of service-specific arguments sent to the media
* @param options The bundle of service-specific arguments sent to the media
* browse service. The contents of this bundle may affect the
* information returned when browsing.
*/
@@ -912,8 +975,8 @@ public final class MediaBrowser {
*
* @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.
* @param options The bundle of service-specific arguments sent to the media
* browser service.
*/
public void onError(@NonNull String parentId, @NonNull Bundle options) {
}
@@ -924,7 +987,7 @@ public final class MediaBrowser {
*/
public static abstract class ItemCallback {
/**
* Called when the item has been returned by the browser service.
* Called when the item has been returned by the connected service.
*
* @param item The item that was returned or null if it doesn't exist.
*/
@@ -932,11 +995,38 @@ public final class MediaBrowser {
}
/**
* Called when the item doesn't exist or there was an error retrieving it.
* Called there was an error retrieving it or the connected service doesn't support
* {@link #getItem}.
*
* @param itemId The media id of the media item which could not be loaded.
* @param mediaId The media id of the media item which could not be loaded.
*/
public void onError(@NonNull String itemId) {
public void onError(@NonNull String mediaId) {
}
}
/**
* Callback for receiving the result of {@link #search}.
*/
public static abstract class SearchCallback {
/**
* Called when the {@link #search} finished successfully.
*
* @param query The search query sent for the search request to the connected service.
* @param extras The bundle of service-specific arguments sent to the connected service.
* @param items The list of media items which contains the search result.
*/
public void onSearchResult(@NonNull String query, Bundle extras,
@NonNull List<MediaItem> items) {
}
/**
* Called when an error happens while {@link #search} or the connected service doesn't
* support {@link #search}.
*
* @param query The search query sent for the search request to the connected service.
* @param extras The bundle of service-specific arguments sent to the connected service.
*/
public void onError(@NonNull String query, Bundle extras) {
}
}

View File

@@ -19,8 +19,10 @@ oneway interface IMediaBrowserService {
void addSubscriptionDeprecated(String uri, IMediaBrowserServiceCallbacks callbacks);
void removeSubscriptionDeprecated(String uri, IMediaBrowserServiceCallbacks callbacks);
void getMediaItem(String uri, in ResultReceiver cb, IMediaBrowserServiceCallbacks callbacks);
void search(String query, in Bundle extras, in ResultReceiver cb,
IMediaBrowserServiceCallbacks callbacks);
void addSubscription(String uri, in IBinder token, in Bundle options,
IMediaBrowserServiceCallbacks callbacks);
void removeSubscription(String uri, in IBinder token, IMediaBrowserServiceCallbacks callbacks);

View File

@@ -52,9 +52,9 @@ import java.util.Iterator;
import java.util.List;
/**
* Base class for media browse services.
* Base class for media browser services.
* <p>
* Media browse services enable applications to browse media content provided by an application
* Media browser services enable applications to browse media content provided by an application
* and ask the application to start playing it. They may also be used to control content that
* is already playing by way of a {@link MediaSession}.
* </p>
@@ -85,18 +85,27 @@ public abstract class MediaBrowserService extends Service {
/**
* A key for passing the MediaItem to the ResultReceiver in getItem.
*
* @hide
*/
public static final String KEY_MEDIA_ITEM = "media_item";
private static final int RESULT_FLAG_OPTION_NOT_HANDLED = 0x00000001;
private static final int RESULT_FLAG_ON_LOAD_ITEM_NOT_IMPLEMENTED = 0x00000002;
/**
* A key for passing the list of MediaItems to the ResultReceiver in search.
* @hide
*/
public static final String KEY_SEARCH_RESULTS = "search_results";
private static final int RESULT_FLAG_OPTION_NOT_HANDLED = 1 << 0;
private static final int RESULT_FLAG_ON_LOAD_ITEM_NOT_IMPLEMENTED = 1 << 1;
private static final int RESULT_FLAG_ON_SEARCH_NOT_IMPLEMENTED = 1 << 2;
private static final int RESULT_ERROR = -1;
private static final int RESULT_OK = 0;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag=true, value = { RESULT_FLAG_OPTION_NOT_HANDLED,
RESULT_FLAG_ON_LOAD_ITEM_NOT_IMPLEMENTED })
RESULT_FLAG_ON_LOAD_ITEM_NOT_IMPLEMENTED, RESULT_FLAG_ON_SEARCH_NOT_IMPLEMENTED })
private @interface ResultFlags { }
private final ArrayMap<IBinder, ConnectionRecord> mConnections = new ArrayMap<>();
@@ -307,10 +316,6 @@ public abstract class MediaBrowserService extends Service {
@Override
public void getMediaItem(final String mediaId, final ResultReceiver receiver,
final IMediaBrowserServiceCallbacks callbacks) {
if (TextUtils.isEmpty(mediaId) || receiver == null) {
return;
}
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -324,6 +329,23 @@ public abstract class MediaBrowserService extends Service {
}
});
}
@Override
public void search(final String query, Bundle extras, ResultReceiver receiver,
final IMediaBrowserServiceCallbacks callbacks) {
mHandler.post(new Runnable() {
@Override
public void run() {
final IBinder b = callbacks.asBinder();
ConnectionRecord connection = mConnections.get(b);
if (connection == null) {
Log.w(TAG, "search for callback that isn't registered query=" + query);
return;
}
performSearch(query, extras, connection, receiver);
}
});
}
}
@Override
@@ -358,7 +380,7 @@ public abstract class MediaBrowserService extends Service {
* @param clientUid The uid of the application which is requesting access to
* browse media.
* @param rootHints An optional bundle of service-specific arguments to send
* to the media browse service when connecting and retrieving the
* to the media browser 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.
* @return The {@link BrowserRoot} for accessing this app's content or null.
@@ -412,8 +434,8 @@ public abstract class MediaBrowserService extends Service {
* @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.
* @param options A bundle of service-specific arguments sent from the media
* browse. The information returned through the result should be
* @param options The bundle of service-specific arguments sent from the media
* browser. The information returned through the result should be
* affected by the contents of this bundle.
*/
public void onLoadChildren(@NonNull String parentId,
@@ -449,6 +471,32 @@ public abstract class MediaBrowserService extends Service {
result.sendResult(null);
}
/**
* Called to get the search result.
* <p>
* Implementations must call {@link Result#sendResult result.sendResult}. If
* the search will be an expensive operation {@link Result#detach result.detach}
* may be called before returning from this function, and then {@link Result#sendResult
* result.sendResult} called when the search has been completed.
* </p><p>
* In case there are no search results, call {@link Result#sendResult} with an empty list.
* In case there are some errors happened, call {@link Result#sendResult result.sendResult}
* with {@code null}, which will invoke {@link MediaBrowser.SearchCallback#onError}.
* </p><p>
* The default implementation will invoke {@link MediaBrowser.SearchCallback#onError}.
* </p>
*
* @param query The search query sent from the media browser. It contains keywords separated
* by space.
* @param extras The bundle of service-specific arguments sent from the media browser.
* @param result The {@link Result} to send the search result.
*/
public void onSearch(@NonNull String query, Bundle extras,
Result<List<MediaBrowser.MediaItem>> result) {
result.setFlags(RESULT_FLAG_ON_SEARCH_NOT_IMPLEMENTED);
result.sendResult(null);
}
/**
* Call to set the media session.
* <p>
@@ -531,8 +579,8 @@ public abstract class MediaBrowserService extends Service {
*
* @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
* @param options The bundle of service-specific arguments to send
* to the media browser. The contents of this bundle may
* contain the information about the change.
*/
public void notifyChildrenChanged(@NonNull String parentId, @NonNull Bundle options) {
@@ -705,12 +753,12 @@ public abstract class MediaBrowserService extends Service {
@Override
void onResultSent(MediaBrowser.MediaItem item, @ResultFlags int flag) {
if ((flag & RESULT_FLAG_ON_LOAD_ITEM_NOT_IMPLEMENTED) != 0) {
receiver.send(-1, null);
receiver.send(RESULT_ERROR, null);
return;
}
Bundle bundle = new Bundle();
bundle.putParcelable(KEY_MEDIA_ITEM, item);
receiver.send(0, bundle);
receiver.send(RESULT_OK, bundle);
}
};
@@ -724,6 +772,34 @@ public abstract class MediaBrowserService extends Service {
}
}
private void performSearch(String query, Bundle extras, final ConnectionRecord connection,
final ResultReceiver receiver) {
final Result<List<MediaBrowser.MediaItem>> result =
new Result<List<MediaBrowser.MediaItem>>(query) {
@Override
void onResultSent(List<MediaBrowser.MediaItem> items, @ResultFlags int flag) {
if ((flag & RESULT_FLAG_ON_SEARCH_NOT_IMPLEMENTED) != 0
|| items == null) {
receiver.send(RESULT_ERROR, null);
return;
}
Bundle bundle = new Bundle();
bundle.putParcelableArray(KEY_SEARCH_RESULTS,
items.toArray(new MediaBrowser.MediaItem[0]));
receiver.send(RESULT_OK, bundle);
}
};
mCurConnection = connection;
onSearch(query, extras, result);
mCurConnection = null;
if (!result.isDone()) {
throw new IllegalStateException("onSearch must call detach() or sendResult()"
+ " before returning for query=" + query);
}
}
/**
* Contains information that the browser service needs to send to the client
* when first connected.