| /* |
| * 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 androidx.mediarouter.media; |
| |
| import static androidx.annotation.RestrictTo.Scope.LIBRARY; |
| |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.os.Bundle; |
| import android.os.Handler; |
| import android.os.Message; |
| |
| import androidx.annotation.GuardedBy; |
| import androidx.annotation.IntDef; |
| import androidx.annotation.NonNull; |
| import androidx.annotation.Nullable; |
| import androidx.annotation.RestrictTo; |
| import androidx.core.util.ObjectsCompat; |
| import androidx.mediarouter.media.MediaRouter.ControlRequestCallback; |
| |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.List; |
| import java.util.concurrent.Executor; |
| |
| /** |
| * 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 can support |
| * {@link MediaRouteProviderDescriptor#supportsDynamicGroupRoute dynamic group} that |
| * allows the user to add or remove a route from the selected route dynamically. |
| * To control dynamic group, {@link DynamicGroupRouteController} returned by |
| * {@link #onCreateDynamicGroupRouteController} is used instead of {@link RouteController}. |
| * </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 { |
| static final int MSG_DELIVER_DESCRIPTOR_CHANGED = 1; |
| 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 (ObjectsCompat.equals(mDiscoveryRequest, request)) { |
| return; |
| } |
| |
| setDiscoveryRequestInternal(request); |
| } |
| |
| final void setDiscoveryRequestInternal(@Nullable MediaRouteDiscoveryRequest request) { |
| mDiscoveryRequest = request; |
| if (!mPendingDiscoveryRequestChange) { |
| mPendingDiscoveryRequestChange = true; |
| mHandler.sendEmptyMessage(MSG_DELIVER_DISCOVERY_REQUEST_CHANGED); |
| } |
| } |
| |
| 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); |
| } |
| } |
| } |
| |
| 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 |
| */ |
| @RestrictTo(LIBRARY) |
| @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); |
| } |
| |
| /** |
| * Creates a {@link DynamicGroupRouteController}. |
| * <p> |
| * It will be called from an app or {@link MediaRouter} when a single route or a single static |
| * group is selected. |
| * </p> |
| * |
| * @param initialMemberRouteId initially selected route's id. |
| * @return {@link DynamicGroupRouteController}. Returns null if there is no such route or |
| * if the route cannot be controlled using the {@link DynamicGroupRouteController} interface. |
| */ |
| @Nullable |
| public DynamicGroupRouteController onCreateDynamicGroupRouteController( |
| @NonNull String initialMemberRouteId) { |
| if (initialMemberRouteId == null) { |
| throw new IllegalArgumentException("initialMemberRouteId cannot be null."); |
| } |
| return null; |
| } |
| |
| /** |
| * 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(int)} 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. |
| * |
| * @deprecated Use {@link #onUnselect(int)} instead. |
| */ |
| @Deprecated |
| 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(@MediaRouter.UnselectReason 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; |
| } |
| } |
| |
| /** |
| * Provides control over a dynamic group route. |
| * A dynamic group route is a group of routes such that a route can be added or removed |
| * from the group by the user dynamically. |
| */ |
| public abstract static class DynamicGroupRouteController extends RouteController { |
| private final Object mLock = new Object(); |
| |
| @GuardedBy("mLock") |
| Executor mExecutor; |
| @GuardedBy("mLock") |
| OnDynamicRoutesChangedListener mListener; |
| @GuardedBy("mLock") |
| MediaRouteDescriptor mPendingGroupRoute; |
| @GuardedBy("mLock") |
| Collection<DynamicRouteDescriptor> mPendingRoutes; |
| |
| /** |
| * Gets the title of the groupable routes section which will be shown to the user. |
| * It is provided by {@link MediaRouteProvider}. |
| * e.g. "Add a device." |
| */ |
| @Nullable |
| public String getGroupableSelectionTitle() { |
| return null; |
| } |
| |
| /** |
| * Gets the title of the transferable routes section which will be shown to the user. |
| * It is provided by {@link MediaRouteProvider}. |
| * {@link MediaRouteProvider}. |
| * e.g. "Play on group." |
| */ |
| @Nullable |
| public String getTransferableSectionTitle() { |
| return null; |
| } |
| |
| /** |
| * Called when a user selects a new set of routes s/he wants the session to be played. |
| */ |
| // TODO: How about making this to onTransfer(@NonNull String routeId)? |
| public abstract void onUpdateMemberRoutes(@Nullable List<String> routeIds); |
| |
| /** |
| * Called when a user adds a route into the casting session. |
| */ |
| public abstract void onAddMemberRoute(@NonNull String routeId); |
| |
| /** |
| * Called when a user removes a route from casting session. |
| */ |
| // TODO: Add @NonNull in the argument (needs current.txt change) |
| public abstract void onRemoveMemberRoute(String routeId); |
| |
| /** |
| * Called by {@link MediaRouter} to set the listener. |
| */ |
| void setOnDynamicRoutesChangedListener( |
| @NonNull Executor executor, |
| @NonNull OnDynamicRoutesChangedListener listener) { |
| synchronized (mLock) { |
| if (executor == null) { |
| throw new NullPointerException("Executor shouldn't be null"); |
| } |
| if (listener == null) { |
| throw new NullPointerException("Listener shouldn't be null"); |
| } |
| mExecutor = executor; |
| mListener = listener; |
| |
| if (mPendingRoutes != null && !mPendingRoutes.isEmpty()) { |
| MediaRouteDescriptor groupRoute = mPendingGroupRoute; |
| Collection<DynamicRouteDescriptor> routes = mPendingRoutes; |
| mPendingGroupRoute = null; |
| mPendingRoutes = null; |
| mExecutor.execute(new Runnable() { |
| @Override |
| public void run() { |
| listener.onRoutesChanged(DynamicGroupRouteController.this, |
| groupRoute, |
| routes); |
| } |
| }); |
| } |
| } |
| } |
| |
| /** |
| * Sets the dynamic route descriptors for routes. |
| * <p> |
| * The dynamic group controller must call this method to notify the current |
| * dynamic group state of routes. |
| * </p> |
| * @param routes The dynamic route descriptors for published routes. |
| * @deprecated Use {@link #notifyDynamicRoutesChanged(MediaRouteDescriptor, Collection)} |
| * instead to notify information of the group. |
| */ |
| @Deprecated |
| public final void notifyDynamicRoutesChanged( |
| final Collection<DynamicRouteDescriptor> routes) { |
| synchronized (mLock) { |
| if (mExecutor != null) { |
| final OnDynamicRoutesChangedListener listener = mListener; |
| mExecutor.execute(new Runnable() { |
| @Override |
| public void run() { |
| listener.onRoutesChanged( |
| DynamicGroupRouteController.this, |
| null, |
| routes); |
| } |
| }); |
| } else { |
| mPendingRoutes = new ArrayList<>(routes); |
| } |
| } |
| } |
| |
| /** |
| * Sets the group route descriptor and the dynamic route descriptors for dynamicRoutes. |
| * <p> |
| * The dynamic group controller must call this method to notify the current |
| * dynamic group state of dynamicRoutes. |
| * </p> |
| * @param groupRoute The media route descriptor describing the dynamic group. |
| * @param dynamicRoutes The dynamic route descriptors for published routes. |
| */ |
| public final void notifyDynamicRoutesChanged( |
| @NonNull MediaRouteDescriptor groupRoute, |
| @NonNull Collection<DynamicRouteDescriptor> dynamicRoutes) { |
| if (groupRoute == null) { |
| throw new NullPointerException("groupRoute must not be null"); |
| } |
| if (dynamicRoutes == null) { |
| throw new NullPointerException("dynamicRoutes must not be null"); |
| } |
| synchronized (mLock) { |
| if (mExecutor != null) { |
| final OnDynamicRoutesChangedListener listener = mListener; |
| mExecutor.execute(new Runnable() { |
| @Override |
| public void run() { |
| listener.onRoutesChanged( |
| DynamicGroupRouteController.this, |
| groupRoute, |
| dynamicRoutes); |
| } |
| }); |
| } else { |
| mPendingGroupRoute = groupRoute; |
| mPendingRoutes = new ArrayList<>(dynamicRoutes); |
| } |
| } |
| } |
| |
| /** |
| * Used to notify media router each route's property changes regarding this |
| * {@link DynamicGroupRouteController} instance. |
| * <p> Here are some examples when this notification is called : |
| * <ul> |
| * <li> a route is newly turned on and it can be grouped with this dynamic group route. |
| * </li> |
| * <li> a route is selecting as a member of this dynamic group route.</li> |
| * <li> a route is selected as a member of this dynamic group route.</li> |
| * <li> a route is unselecting.</li> |
| * <li> a route is unselected.</li> |
| * <li> a route is turned off.</li> |
| * </ul> |
| * </p> |
| */ |
| interface OnDynamicRoutesChangedListener { |
| /** |
| * The provider should call this method when routes' properties change. |
| * (e.g. when a route becomes ungroupable) |
| * |
| * @param controller the {@link DynamicGroupRouteController} which keeps this listener. |
| * @param groupRoute the route descriptor about the dynamic group. |
| * @param routes the collection of routes contains selected routes. |
| * (can be unselectable or not) |
| * and unselected routes (can be groupable or transferable or not). |
| */ |
| void onRoutesChanged( |
| DynamicGroupRouteController controller, |
| MediaRouteDescriptor groupRoute, |
| Collection<DynamicRouteDescriptor> routes); |
| } |
| |
| /** |
| * Contains a route, its selection state and its capabilities. |
| */ |
| public static final class DynamicRouteDescriptor { |
| static final String KEY_MEDIA_ROUTE_DESCRIPTOR = "mrDescriptor"; |
| static final String KEY_SELECTION_STATE = "selectionState"; |
| static final String KEY_IS_UNSELECTABLE = "isUnselectable"; |
| static final String KEY_IS_GROUPABLE = "isGroupable"; |
| static final String KEY_IS_TRANSFERABLE = "isTransferable"; |
| |
| /** |
| * @hide |
| */ |
| @RestrictTo(LIBRARY) |
| @IntDef({ |
| UNSELECTING, |
| UNSELECTED, |
| SELECTING, |
| SELECTED |
| }) |
| @Retention(RetentionPolicy.SOURCE) |
| public @interface SelectionState {} |
| /** |
| * After a user unselects a route, it might take some time for a provider to complete |
| * the operation. This state is used in this between time. MediaRouter can either |
| * block the UI or show the route as unchecked. |
| */ |
| public static final int UNSELECTING = 0; |
| |
| /** |
| * The route is unselected. |
| * <p> |
| * Unselect operation is done by the route provider. |
| * </p> |
| */ |
| public static final int UNSELECTED = 1; |
| |
| /** |
| * After a user selects a route, it might take some time for a provider to complete |
| * the operation. This state is used in this between time. MediaRouter can either |
| * block the UI or show the route as checked. |
| */ |
| public static final int SELECTING = 2; |
| |
| /** |
| * The route is selected. |
| * <p> |
| * Select operation is done by the route provider. |
| * </p> |
| */ |
| public static final int SELECTED = 3; |
| |
| //TODO: mMediaRouteDescriptor could have an old info. We should provide a way to |
| // update it or use only the route ID. |
| final MediaRouteDescriptor mMediaRouteDescriptor; |
| @SelectionState |
| final int mSelectionState; |
| final boolean mIsUnselectable; |
| final boolean mIsGroupable; |
| final boolean mIsTransferable; |
| Bundle mBundle; |
| |
| DynamicRouteDescriptor( |
| MediaRouteDescriptor mediaRouteDescriptor, @SelectionState int selectionState, |
| boolean isUnselectable, boolean isGroupable, boolean isTransferable) { |
| mMediaRouteDescriptor = mediaRouteDescriptor; |
| mSelectionState = selectionState; |
| mIsUnselectable = isUnselectable; |
| mIsGroupable = isGroupable; |
| mIsTransferable = isTransferable; |
| } |
| |
| /** |
| * Gets this route's {@link MediaRouteDescriptor}. i.e. which route this info is for. |
| */ |
| @NonNull |
| public MediaRouteDescriptor getRouteDescriptor() { |
| return mMediaRouteDescriptor; |
| } |
| |
| /** |
| * Gets the selection state. |
| */ |
| public @SelectionState int getSelectionState() { |
| return mSelectionState; |
| } |
| |
| /** |
| * Returns true if the route can be unselected. |
| * <p> |
| * For example, a static group has an old build which doesn't support dynamic group. |
| * All its members can't be removed. |
| * </p> |
| * <p> |
| * Only applicable to selected/selecting routes. |
| * </p> |
| */ |
| public boolean isUnselectable() { |
| return mIsUnselectable; |
| } |
| |
| /** |
| * Returns true if the route can be grouped into the dynamic group route. |
| * <p> |
| * Only applicable to unselected/unselecting routes. |
| * Note that {@link #isGroupable()} and {@link #isTransferable()} are NOT mutually |
| * exclusive. |
| * </p> |
| */ |
| public boolean isGroupable() { |
| return mIsGroupable; |
| } |
| |
| /** |
| * Returns true if the current dynamic group route can be transferred to this route. |
| * <p> |
| * Only applicable to unselected/unselecting routes. |
| * Note that {@link #isGroupable()} and {@link #isTransferable()} are NOT mutually |
| * exclusive. |
| * </p> |
| */ |
| public boolean isTransferable() { |
| return mIsTransferable; |
| } |
| |
| Bundle toBundle() { |
| if (mBundle == null) { |
| mBundle = new Bundle(); |
| mBundle.putBundle(KEY_MEDIA_ROUTE_DESCRIPTOR, mMediaRouteDescriptor.asBundle()); |
| mBundle.putInt(KEY_SELECTION_STATE, mSelectionState); |
| mBundle.putBoolean(KEY_IS_UNSELECTABLE, mIsUnselectable); |
| mBundle.putBoolean(KEY_IS_GROUPABLE, mIsGroupable); |
| mBundle.putBoolean(KEY_IS_TRANSFERABLE, mIsTransferable); |
| } |
| return mBundle; |
| } |
| |
| static DynamicRouteDescriptor fromBundle(Bundle bundle) { |
| if (bundle == null) { |
| return null; |
| } |
| MediaRouteDescriptor descriptor = MediaRouteDescriptor.fromBundle( |
| bundle.getBundle(KEY_MEDIA_ROUTE_DESCRIPTOR)); |
| int selectionState = bundle.getInt(KEY_SELECTION_STATE, UNSELECTED); |
| boolean isUnselectable = bundle.getBoolean(KEY_IS_UNSELECTABLE, false); |
| boolean isGroupable = bundle.getBoolean(KEY_IS_GROUPABLE, false); |
| boolean isTransferable = bundle.getBoolean(KEY_IS_TRANSFERABLE, false); |
| return new DynamicRouteDescriptor(descriptor, selectionState, isUnselectable, |
| isGroupable, isTransferable); |
| } |
| |
| /** |
| * Builder for {@link DynamicRouteDescriptor} |
| */ |
| public static final class Builder { |
| private final MediaRouteDescriptor mRouteDescriptor; |
| private @SelectionState int mSelectionState = UNSELECTED; |
| private boolean mIsUnselectable = false; |
| private boolean mIsGroupable = false; |
| private boolean mIsTransferable = false; |
| |
| /** |
| * A constructor with {@link MediaRouteDescriptor}. |
| */ |
| public Builder(MediaRouteDescriptor descriptor) { |
| mRouteDescriptor = descriptor; |
| } |
| |
| /** |
| * Copies the properties from the given {@link DynamicRouteDescriptor} |
| */ |
| public Builder(DynamicRouteDescriptor dynamicRouteDescriptor) { |
| mRouteDescriptor = dynamicRouteDescriptor.getRouteDescriptor(); |
| mSelectionState = dynamicRouteDescriptor.getSelectionState(); |
| mIsUnselectable = dynamicRouteDescriptor.isUnselectable(); |
| mIsGroupable = dynamicRouteDescriptor.isGroupable(); |
| mIsTransferable = dynamicRouteDescriptor.isTransferable(); |
| } |
| |
| /** |
| * Sets the selection state of this route within the associated dynamic group route. |
| */ |
| public Builder setSelectionState(@SelectionState int state) { |
| mSelectionState = state; |
| return this; |
| } |
| |
| /** |
| * Sets if this route can be unselected. |
| */ |
| public Builder setIsUnselectable(boolean value) { |
| mIsUnselectable = value; |
| return this; |
| } |
| |
| /** |
| * Sets if this route can be a selected as a member of the associated dynamic |
| * group route. |
| */ |
| public Builder setIsGroupable(boolean value) { |
| mIsGroupable = value; |
| return this; |
| } |
| |
| /** |
| * Sets if the associated dynamic group route can be transferred to this route. |
| */ |
| public Builder setIsTransferable(boolean value) { |
| mIsTransferable = value; |
| return this; |
| } |
| |
| /** |
| * Builds the {@link DynamicRouteDescriptor}. |
| */ |
| public DynamicRouteDescriptor build() { |
| return new DynamicRouteDescriptor( |
| mRouteDescriptor, mSelectionState, mIsUnselectable, mIsGroupable, |
| mIsTransferable); |
| } |
| } |
| } |
| } |
| |
| /** |
| * 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 change. |
| * |
| * @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 { |
| ProviderHandler() { |
| } |
| |
| @Override |
| public void handleMessage(Message msg) { |
| switch (msg.what) { |
| case MSG_DELIVER_DESCRIPTOR_CHANGED: |
| deliverDescriptorChanged(); |
| break; |
| case MSG_DELIVER_DISCOVERY_REQUEST_CHANGED: |
| deliverDiscoveryRequestChanged(); |
| break; |
| } |
| } |
| } |
| } |