blob: a9c3ed2c12be2ee865f6f1365f6e77bedb2bf635 [file] [log] [blame]
/*
* Copyright (C) 2013 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.support.v7.media;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.Message;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.media.MediaRouter.ControlRequestCallback;
/**
* Media route providers are used to publish additional media routes for
* use within an application. Media route providers may also be declared
* as a service to publish additional media routes to all applications
* in the system.
* <p>
* The purpose of a media route provider is to discover media routes that satisfy
* the criteria specified by the current {@link MediaRouteDiscoveryRequest} and publish a
* {@link MediaRouteProviderDescriptor} with information about each route by calling
* {@link #setDescriptor} to notify the currently registered {@link Callback}.
* </p><p>
* The provider should watch for changes to the discovery request by implementing
* {@link #onDiscoveryRequestChanged} and updating the set of routes that it is
* attempting to discover. It should also handle route control requests such
* as volume changes or {@link MediaControlIntent media control intents}
* by implementing {@link #onCreateRouteController} to return a {@link RouteController}
* for a particular route.
* </p><p>
* A media route provider may be used privately within the scope of a single
* application process by calling {@link MediaRouter#addProvider MediaRouter.addProvider}
* to add it to the local {@link MediaRouter}. A media route provider may also be made
* available globally to all applications by registering a {@link MediaRouteProviderService}
* in the provider's manifest. When the media route provider is registered
* as a service, all applications that use the media router API will be able to
* discover and used the provider's routes without having to install anything else.
* </p><p>
* This object must only be accessed on the main thread.
* </p>
*/
public abstract class MediaRouteProvider {
private static final int MSG_DELIVER_DESCRIPTOR_CHANGED = 1;
private static final int MSG_DELIVER_DISCOVERY_REQUEST_CHANGED = 2;
private final Context mContext;
private final ProviderMetadata mMetadata;
private final ProviderHandler mHandler = new ProviderHandler();
private Callback mCallback;
private MediaRouteDiscoveryRequest mDiscoveryRequest;
private boolean mPendingDiscoveryRequestChange;
private MediaRouteProviderDescriptor mDescriptor;
private boolean mPendingDescriptorChange;
/**
* Creates a media route provider.
*
* @param context The context.
*/
public MediaRouteProvider(@NonNull Context context) {
this(context, null);
}
MediaRouteProvider(Context context, ProviderMetadata metadata) {
if (context == null) {
throw new IllegalArgumentException("context must not be null");
}
mContext = context;
if (metadata == null) {
mMetadata = new ProviderMetadata(new ComponentName(context, getClass()));
} else {
mMetadata = metadata;
}
}
/**
* Gets the context of the media route provider.
*/
public final Context getContext() {
return mContext;
}
/**
* Gets the provider's handler which is associated with the main thread.
*/
public final Handler getHandler() {
return mHandler;
}
/**
* Gets some metadata about the provider's implementation.
*/
public final ProviderMetadata getMetadata() {
return mMetadata;
}
/**
* Sets a callback to invoke when the provider's descriptor changes.
*
* @param callback The callback to use, or null if none.
*/
public final void setCallback(@Nullable Callback callback) {
MediaRouter.checkCallingThread();
mCallback = callback;
}
/**
* Gets the current discovery request which informs the provider about the
* kinds of routes to discover and whether to perform active scanning.
*
* @return The current discovery request, or null if no discovery is needed at this time.
*
* @see #onDiscoveryRequestChanged
*/
@Nullable
public final MediaRouteDiscoveryRequest getDiscoveryRequest() {
return mDiscoveryRequest;
}
/**
* Sets a discovery request to inform the provider about the kinds of
* routes that its clients would like to discover and whether to perform active scanning.
*
* @param request The discovery request, or null if no discovery is needed at this time.
*
* @see #onDiscoveryRequestChanged
*/
public final void setDiscoveryRequest(MediaRouteDiscoveryRequest request) {
MediaRouter.checkCallingThread();
if (mDiscoveryRequest == request
|| (mDiscoveryRequest != null && mDiscoveryRequest.equals(request))) {
return;
}
mDiscoveryRequest = request;
if (!mPendingDiscoveryRequestChange) {
mPendingDiscoveryRequestChange = true;
mHandler.sendEmptyMessage(MSG_DELIVER_DISCOVERY_REQUEST_CHANGED);
}
}
private void deliverDiscoveryRequestChanged() {
mPendingDiscoveryRequestChange = false;
onDiscoveryRequestChanged(mDiscoveryRequest);
}
/**
* Called by the media router when the {@link MediaRouteDiscoveryRequest discovery request}
* has changed.
* <p>
* Whenever an applications calls {@link MediaRouter#addCallback} to register
* a callback, it also provides a selector to specify the kinds of routes that
* it is interested in. The media router combines all of these selectors together
* to generate a {@link MediaRouteDiscoveryRequest} and notifies each provider when a change
* occurs by calling {@link #setDiscoveryRequest} which posts a message to invoke
* this method asynchronously.
* </p><p>
* The provider should examine the {@link MediaControlIntent media control categories}
* in the discovery request's {@link MediaRouteSelector selector} to determine what
* kinds of routes it should try to discover and whether it should perform active
* or passive scans. In many cases, the provider may be able to save power by
* determining that the selector does not contain any categories that it supports
* and it can therefore avoid performing any scans at all.
* </p>
*
* @param request The new discovery request, or null if no discovery is needed at this time.
*
* @see MediaRouter#addCallback
*/
public void onDiscoveryRequestChanged(@Nullable MediaRouteDiscoveryRequest request) {
}
/**
* Gets the provider's descriptor.
* <p>
* The descriptor describes the state of the media route provider and
* the routes that it publishes. Watch for changes to the descriptor
* by registering a {@link Callback callback} with {@link #setCallback}.
* </p>
*
* @return The media route provider descriptor, or null if none.
*
* @see Callback#onDescriptorChanged
*/
@Nullable
public final MediaRouteProviderDescriptor getDescriptor() {
return mDescriptor;
}
/**
* Sets the provider's descriptor.
* <p>
* The provider must call this method to notify the currently registered
* {@link Callback callback} about the change to the provider's descriptor.
* </p>
*
* @param descriptor The updated route provider descriptor, or null if none.
*
* @see Callback#onDescriptorChanged
*/
public final void setDescriptor(@Nullable MediaRouteProviderDescriptor descriptor) {
MediaRouter.checkCallingThread();
if (mDescriptor != descriptor) {
mDescriptor = descriptor;
if (!mPendingDescriptorChange) {
mPendingDescriptorChange = true;
mHandler.sendEmptyMessage(MSG_DELIVER_DESCRIPTOR_CHANGED);
}
}
}
private void deliverDescriptorChanged() {
mPendingDescriptorChange = false;
if (mCallback != null) {
mCallback.onDescriptorChanged(this, mDescriptor);
}
}
/**
* Called by the media router to obtain a route controller for a particular route.
* <p>
* The media router will invoke the {@link RouteController#onRelease} method of the route
* controller when it is no longer needed to allow it to free its resources.
* </p>
*
* @param routeId The unique id of the route.
* @return The route controller. Returns null if there is no such route or if the route
* cannot be controlled using the route controller interface.
*/
@Nullable
public RouteController onCreateRouteController(@NonNull String routeId) {
if (routeId == null) {
throw new IllegalArgumentException("routeId cannot be null");
}
return null;
}
/**
* Called by the media router to obtain a route controller for a particular route which is a
* member of {@link MediaRouter.RouteGroup}.
* <p>
* The media router will invoke the {@link RouteController#onRelease} method of the route
* controller when it is no longer needed to allow it to free its resources.
* </p>
*
* @param routeId The unique id of the member route.
* @param routeGroupId The unique id of the route group.
* @return The route controller. Returns null if there is no such route or if the route
* cannot be controlled using the route controller interface.
* @hide
*/
@Nullable
public RouteController onCreateRouteController(@NonNull String routeId,
@NonNull String routeGroupId) {
if (routeId == null) {
throw new IllegalArgumentException("routeId cannot be null");
}
if (routeGroupId == null) {
throw new IllegalArgumentException("routeGroupId cannot be null");
}
return onCreateRouteController(routeId);
}
/**
* Describes properties of the route provider's implementation.
* <p>
* This object is immutable once created.
* </p>
*/
public static final class ProviderMetadata {
private final ComponentName mComponentName;
ProviderMetadata(ComponentName componentName) {
if (componentName == null) {
throw new IllegalArgumentException("componentName must not be null");
}
mComponentName = componentName;
}
/**
* Gets the provider's package name.
*/
public String getPackageName() {
return mComponentName.getPackageName();
}
/**
* Gets the provider's component name.
*/
public ComponentName getComponentName() {
return mComponentName;
}
@Override
public String toString() {
return "ProviderMetadata{ componentName="
+ mComponentName.flattenToShortString() + " }";
}
}
/**
* Provides control over a particular route.
* <p>
* The media router obtains a route controller for a route whenever it needs
* to control a route. When a route is selected, the media router invokes
* the {@link #onSelect} method of its route controller. While selected,
* the media router may call other methods of the route controller to
* request that it perform certain actions to the route. When a route is
* unselected, the media router invokes the {@link #onUnselect} method of its
* route controller. When the media route no longer needs the route controller
* it will invoke the {@link #onRelease} method to allow the route controller
* to free its resources.
* </p><p>
* There may be multiple route controllers simultaneously active for the
* same route. Each route controller will be released separately.
* </p><p>
* All operations on the route controller are asynchronous and
* results are communicated via callbacks.
* </p>
*/
public static abstract class RouteController {
/**
* Releases the route controller, allowing it to free its resources.
*/
public void onRelease() {
}
/**
* Selects the route.
*/
public void onSelect() {
}
/**
* Unselects the route.
*/
public void onUnselect() {
}
/**
* Unselects the route and provides a reason. The default implementation
* calls {@link #onUnselect()}.
* <p>
* The reason provided will be one of the following:
* <ul>
* <li>{@link MediaRouter#UNSELECT_REASON_UNKNOWN}</li>
* <li>{@link MediaRouter#UNSELECT_REASON_DISCONNECTED}</li>
* <li>{@link MediaRouter#UNSELECT_REASON_STOPPED}</li>
* <li>{@link MediaRouter#UNSELECT_REASON_ROUTE_CHANGED}</li>
* </ul>
*
* @param reason The reason for unselecting the route.
*/
public void onUnselect(int reason) {
onUnselect();
}
/**
* Requests to set the volume of the route.
*
* @param volume The new volume value between 0 and {@link MediaRouteDescriptor#getVolumeMax}.
*/
public void onSetVolume(int volume) {
}
/**
* Requests an incremental volume update for the route.
*
* @param delta The delta to add to the current volume.
*/
public void onUpdateVolume(int delta) {
}
/**
* Performs a {@link MediaControlIntent media control} request
* asynchronously on behalf of the route.
*
* @param intent A {@link MediaControlIntent media control intent}.
* @param callback A {@link ControlRequestCallback} to invoke with the result
* of the request, or null if no result is required.
* @return True if the controller intends to handle the request and will
* invoke the callback when finished. False if the controller will not
* handle the request and will not invoke the callback.
*
* @see MediaControlIntent
*/
public boolean onControlRequest(Intent intent, @Nullable ControlRequestCallback callback) {
return false;
}
}
/**
* Callback which is invoked when route information becomes available or changes.
*/
public static abstract class Callback {
/**
* Called when information about a route provider and its routes changes.
*
* @param provider The media route provider that changed, never null.
* @param descriptor The new media route provider descriptor, or null if none.
*/
public void onDescriptorChanged(@NonNull MediaRouteProvider provider,
@Nullable MediaRouteProviderDescriptor descriptor) {
}
}
private final class ProviderHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_DELIVER_DESCRIPTOR_CHANGED:
deliverDescriptorChanged();
break;
case MSG_DELIVER_DISCOVERY_REQUEST_CHANGED:
deliverDiscoveryRequestChanged();
break;
}
}
}
}