/* * Copyright (C) 2014 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.service.media; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.UnsupportedAppUsage; import android.app.Service; import android.content.Intent; import android.content.pm.PackageManager; import android.media.MediaParceledListSlice; import android.media.browse.MediaBrowser; import android.media.browse.MediaBrowserUtils; import android.media.session.MediaSession; import android.media.session.MediaSessionManager; import android.media.session.MediaSessionManager.RemoteUserInfo; import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.os.ResultReceiver; import android.util.ArrayMap; import android.util.Log; import android.util.Pair; import java.io.FileDescriptor; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; /** * Base class for media browser services. *
* 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}. *
* * To extend this class, you must declare the service in your manifest file with * an intent filter with the {@link #SERVICE_INTERFACE} action. * * For example: ** <service android:name=".MyMediaBrowserService" * android:label="@string/service_name" > * <intent-filter> * <action android:name="android.media.browse.MediaBrowserService" /> * </intent-filter> * </service> ** */ public abstract class MediaBrowserService extends Service { private static final String TAG = "MediaBrowserService"; private static final boolean DBG = false; /** * The {@link Intent} that must be declared as handled by the service. */ @SdkConstant(SdkConstantType.SERVICE_ACTION) public static final String SERVICE_INTERFACE = "android.media.browse.MediaBrowserService"; /** * A key for passing the MediaItem to the ResultReceiver in getItem. * @hide */ @UnsupportedAppUsage public static final String KEY_MEDIA_ITEM = "media_item"; 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_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 }) private @interface ResultFlags { } private final ArrayMap
* Each of the methods that takes one of these to send the result must call
* {@link #sendResult} to respond to the caller with the given results. If those
* functions return without calling {@link #sendResult}, they must instead call
* {@link #detach} before returning, and then may call {@link #sendResult} when
* they are done. If more than one of those methods is called, an exception will
* be thrown.
*
* @see #onLoadChildren
* @see #onLoadItem
*/
public class Result
* The implementation should verify that the client package has permission
* to access browse media information before returning the root id; it
* should return null if the client is not allowed to access this
* information.
*
* 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.
*
* In case the media item does not have any children, call {@link Result#sendResult}
* with an empty list. When the given {@code parentId} is invalid, implementations must
* call {@link Result#sendResult result.sendResult} with {@code null}, which will invoke
* {@link MediaBrowser.SubscriptionCallback#onError}.
*
* 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.
*
* In case the media item does not have any children, call {@link Result#sendResult}
* with an empty list. When the given {@code parentId} is invalid, implementations must
* call {@link Result#sendResult result.sendResult} with {@code null}, which will invoke
* {@link MediaBrowser.SubscriptionCallback#onError}.
*
* Implementations must call {@link Result#sendResult result.sendResult}. If
* loading the item 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 item has
* been loaded.
*
* When the given {@code itemId} is invalid, implementations must call
* {@link Result#sendResult result.sendResult} with {@code null}.
*
* The default implementation will invoke {@link MediaBrowser.ItemCallback#onError}.
*
* This should be called as soon as possible during the service's startup.
* It may only be called once.
*
* @param token The token for the service's {@link MediaSession}.
*/
public void setSessionToken(final MediaSession.Token token) {
if (token == null) {
throw new IllegalArgumentException("Session token may not be null.");
}
if (mSession != null) {
throw new IllegalStateException("The session token has already been set.");
}
mSession = token;
mHandler.post(new Runnable() {
@Override
public void run() {
Iterator
* Callers must make sure that this connection is still connected.
*/
private void performLoadChildren(final String parentId, final ConnectionRecord connection,
final Bundle options) {
final Result When creating a media browser for a given media browser service, this key can be
* supplied as a root hint for retrieving media items that are recently played.
* If the media browser service can provide such media items, the implementation must return
* the key in the root hint when {@link #onGetRoot(String, int, Bundle)} is called back.
*
* The root hint may contain multiple keys.
*
* @see #EXTRA_OFFLINE
* @see #EXTRA_SUGGESTED
*/
public static final String EXTRA_RECENT = "android.service.media.extra.RECENT";
/**
* The lookup key for a boolean that indicates whether the browser service should return a
* browser root for offline media items.
*
* When creating a media browser for a given media browser service, this key can be
* supplied as a root hint for retrieving media items that are can be played without an
* internet connection.
* If the media browser service can provide such media items, the implementation must return
* the key in the root hint when {@link #onGetRoot(String, int, Bundle)} is called back.
*
* The root hint may contain multiple keys.
*
* @see #EXTRA_RECENT
* @see #EXTRA_SUGGESTED
*/
public static final String EXTRA_OFFLINE = "android.service.media.extra.OFFLINE";
/**
* The lookup key for a boolean that indicates whether the browser service should return a
* browser root for suggested media items.
*
* When creating a media browser for a given media browser service, this key can be
* supplied as a root hint for retrieving the media items suggested by the media browser
* service. The list of media items passed in {@link android.media.browse.MediaBrowser.SubscriptionCallback#onChildrenLoaded(String, List)}
* is considered ordered by relevance, first being the top suggestion.
* If the media browser service can provide such media items, the implementation must return
* the key in the root hint when {@link #onGetRoot(String, int, Bundle)} is called back.
*
* The root hint may contain multiple keys.
*
* @see #EXTRA_RECENT
* @see #EXTRA_OFFLINE
*/
public static final String EXTRA_SUGGESTED = "android.service.media.extra.SUGGESTED";
private final String mRootId;
private final Bundle mExtras;
/**
* Constructs a browser root.
* @param rootId The root id for browsing.
* @param extras Any extras about the browser service.
*/
public BrowserRoot(@NonNull String rootId, @Nullable Bundle extras) {
if (rootId == null) {
throw new IllegalArgumentException("The root id in BrowserRoot cannot be null. "
+ "Use null for BrowserRoot instead.");
}
mRootId = rootId;
mExtras = extras;
}
/**
* Gets the root id for browsing.
*/
public String getRootId() {
return mRootId;
}
/**
* Gets any extras about the browser service.
*/
public Bundle getExtras() {
return mExtras;
}
}
}
> result);
/**
* Called to get information about the children of a media item.
*
> 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.setFlags(RESULT_FLAG_OPTION_NOT_HANDLED);
onLoadChildren(parentId, result);
}
/**
* Called to get information about a specific media item.
*
> result =
new Result
>(parentId) {
@Override
void onResultSent(List