/*
* Copyright (C) 2012 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;
import android.bluetooth.BluetoothA2dp;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.util.Log;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* MediaRouter allows applications to control the routing of media channels
* and streams from the current device to external speakers and destination devices.
*
*
A MediaRouter is retrieved through {@link Context#getSystemService(String)
* Context.getSystemService()} of a {@link Context#MEDIA_ROUTER_SERVICE
* Context.MEDIA_ROUTER_SERVICE}.
*
*
The media router API is not thread-safe; all interactions with it must be
* done from the main thread of the process.
*/
public class MediaRouter {
private static final String TAG = "MediaRouter";
static class Static {
private final Resources mResources;
private final AudioManager mAudioManager;
private final Handler mHandler;
private final ArrayList mCallbacks = new ArrayList();
private final ArrayList mRoutes = new ArrayList();
private final ArrayList mCategories = new ArrayList();
private final RouteCategory mSystemCategory;
private final HeadphoneChangedBroadcastReceiver mHeadphoneChangedReceiver;
private RouteInfo mDefaultAudio;
private RouteInfo mBluetoothA2dpRoute;
private RouteInfo mSelectedRoute;
Static(Context appContext) {
mResources = Resources.getSystem();
mHandler = new Handler(appContext.getMainLooper());
mAudioManager = (AudioManager)appContext.getSystemService(Context.AUDIO_SERVICE);
// XXX this doesn't deal with locale changes!
mSystemCategory = new RouteCategory(mResources.getText(
com.android.internal.R.string.default_audio_route_category_name),
ROUTE_TYPE_LIVE_AUDIO, false);
final IntentFilter speakerFilter = new IntentFilter(Intent.ACTION_HEADSET_PLUG);
speakerFilter.addAction(Intent.ACTION_ANALOG_AUDIO_DOCK_PLUG);
speakerFilter.addAction(Intent.ACTION_DIGITAL_AUDIO_DOCK_PLUG);
speakerFilter.addAction(Intent.ACTION_HDMI_AUDIO_PLUG);
mHeadphoneChangedReceiver = new HeadphoneChangedBroadcastReceiver();
appContext.registerReceiver(mHeadphoneChangedReceiver, speakerFilter);
mDefaultAudio = new RouteInfo(mSystemCategory);
mDefaultAudio.mName = mResources.getText(
com.android.internal.R.string.default_audio_route_name);
mDefaultAudio.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO;
addRoute(mDefaultAudio);
}
}
static Static sStatic;
/**
* Route type flag for live audio.
*
* A device that supports live audio routing will allow the media audio stream
* to be routed to supported destinations. This can include internal speakers or
* audio jacks on the device itself, A2DP devices, and more.
*
* Once initiated this routing is transparent to the application. All audio
* played on the media stream will be routed to the selected destination.
*/
public static final int ROUTE_TYPE_LIVE_AUDIO = 0x1;
/**
* Route type flag for application-specific usage.
*
* Unlike other media route types, user routes are managed by the application.
* The MediaRouter will manage and dispatch events for user routes, but the application
* is expected to interpret the meaning of these events and perform the requested
* routing tasks.
*/
public static final int ROUTE_TYPE_USER = 0x00800000;
// Maps application contexts
static final HashMap sRouters = new HashMap();
static String typesToString(int types) {
final StringBuilder result = new StringBuilder();
if ((types & ROUTE_TYPE_LIVE_AUDIO) != 0) {
result.append("ROUTE_TYPE_LIVE_AUDIO ");
}
if ((types & ROUTE_TYPE_USER) != 0) {
result.append("ROUTE_TYPE_USER ");
}
return result.toString();
}
/** @hide */
public MediaRouter(Context context) {
synchronized (Static.class) {
if (sStatic == null) {
sStatic = new Static(context.getApplicationContext());
}
}
}
/**
* @hide for use by framework routing UI
*/
public RouteInfo getSystemAudioRoute() {
return sStatic.mDefaultAudio;
}
/**
* Return the currently selected route for the given types
*
* @param type route types
* @return the selected route
*/
public RouteInfo getSelectedRoute(int type) {
return sStatic.mSelectedRoute;
}
static void onHeadphonesPlugged(boolean headphonesPresent, String headphonesName) {
sStatic.mDefaultAudio.mName = headphonesPresent
? headphonesName
: sStatic.mResources.getText(
com.android.internal.R.string.default_audio_route_name);
dispatchRouteChanged(sStatic.mDefaultAudio);
}
/**
* Add a callback to listen to events about specific kinds of media routes.
* If the specified callback is already registered, its registration will be updated for any
* additional route types specified.
*
* @param types Types of routes this callback is interested in
* @param cb Callback to add
*/
public void addCallback(int types, Callback cb) {
final int count = sStatic.mCallbacks.size();
for (int i = 0; i < count; i++) {
final CallbackInfo info = sStatic.mCallbacks.get(i);
if (info.cb == cb) {
info.type &= types;
return;
}
}
sStatic.mCallbacks.add(new CallbackInfo(cb, types, this));
}
/**
* Remove the specified callback. It will no longer receive events about media routing.
*
* @param cb Callback to remove
*/
public void removeCallback(Callback cb) {
final int count = sStatic.mCallbacks.size();
for (int i = 0; i < count; i++) {
if (sStatic.mCallbacks.get(i).cb == cb) {
sStatic.mCallbacks.remove(i);
return;
}
}
Log.w(TAG, "removeCallback(" + cb + "): callback not registered");
}
/**
* Select the specified route to use for output of the given media types.
*
* @param types type flags indicating which types this route should be used for.
* The route must support at least a subset.
* @param route Route to select
*/
public void selectRoute(int types, RouteInfo route) {
selectRouteStatic(types, route);
}
static void selectRouteStatic(int types, RouteInfo route) {
if (sStatic.mSelectedRoute == route) return;
if (sStatic.mSelectedRoute != null) {
// TODO filter types properly
dispatchRouteUnselected(types & sStatic.mSelectedRoute.getSupportedTypes(),
sStatic.mSelectedRoute);
}
sStatic.mSelectedRoute = route;
if (route != null) {
// TODO filter types properly
dispatchRouteSelected(types & route.getSupportedTypes(), route);
}
}
/**
* Add an app-specified route for media to the MediaRouter.
* App-specified route definitions are created using {@link #createUserRoute(RouteCategory)}
*
* @param info Definition of the route to add
* @see #createUserRoute()
* @see #removeUserRoute(UserRouteInfo)
*/
public void addUserRoute(UserRouteInfo info) {
addRoute(info);
}
static void addRoute(RouteInfo info) {
final RouteCategory cat = info.getCategory();
if (!sStatic.mCategories.contains(cat)) {
sStatic.mCategories.add(cat);
}
final boolean onlyRoute = sStatic.mRoutes.isEmpty();
if (cat.isGroupable() && !(info instanceof RouteGroup)) {
// Enforce that any added route in a groupable category must be in a group.
final RouteGroup group = new RouteGroup(info.getCategory());
sStatic.mRoutes.add(group);
dispatchRouteAdded(group);
final int at = group.getRouteCount();
group.addRoute(info);
dispatchRouteGrouped(info, group, at);
info = group;
} else {
sStatic.mRoutes.add(info);
dispatchRouteAdded(info);
}
if (onlyRoute) {
selectRouteStatic(info.getSupportedTypes(), info);
}
}
/**
* Remove an app-specified route for media from the MediaRouter.
*
* @param info Definition of the route to remove
* @see #addUserRoute(UserRouteInfo)
*/
public void removeUserRoute(UserRouteInfo info) {
removeRoute(info);
}
/**
* Remove all app-specified routes from the MediaRouter.
*
* @see #removeUserRoute(UserRouteInfo)
*/
public void clearUserRoutes() {
for (int i = 0; i < sStatic.mRoutes.size(); i++) {
final RouteInfo info = sStatic.mRoutes.get(i);
if (info instanceof UserRouteInfo) {
removeRouteAt(i);
i--;
}
}
}
static void removeRoute(RouteInfo info) {
if (sStatic.mRoutes.remove(info)) {
final RouteCategory removingCat = info.getCategory();
final int count = sStatic.mRoutes.size();
boolean found = false;
for (int i = 0; i < count; i++) {
final RouteCategory cat = sStatic.mRoutes.get(i).getCategory();
if (removingCat == cat) {
found = true;
break;
}
}
if (!found) {
sStatic.mCategories.remove(removingCat);
}
dispatchRouteRemoved(info);
}
}
void removeRouteAt(int routeIndex) {
if (routeIndex >= 0 && routeIndex < sStatic.mRoutes.size()) {
final RouteInfo info = sStatic.mRoutes.remove(routeIndex);
final RouteCategory removingCat = info.getCategory();
final int count = sStatic.mRoutes.size();
boolean found = false;
for (int i = 0; i < count; i++) {
final RouteCategory cat = sStatic.mRoutes.get(i).getCategory();
if (removingCat == cat) {
found = true;
break;
}
}
if (!found) {
sStatic.mCategories.remove(removingCat);
}
dispatchRouteRemoved(info);
}
}
/**
* Return the number of {@link MediaRouter.RouteCategory categories} currently
* represented by routes known to this MediaRouter.
*
* @return the number of unique categories represented by this MediaRouter's known routes
*/
public int getCategoryCount() {
return sStatic.mCategories.size();
}
/**
* Return the {@link MediaRouter.RouteCategory category} at the given index.
* Valid indices are in the range [0-getCategoryCount).
*
* @param index which category to return
* @return the category at index
*/
public RouteCategory getCategoryAt(int index) {
return sStatic.mCategories.get(index);
}
/**
* Return the number of {@link MediaRouter.RouteInfo routes} currently known
* to this MediaRouter.
*
* @return the number of routes tracked by this router
*/
public int getRouteCount() {
return sStatic.mRoutes.size();
}
/**
* Return the route at the specified index.
*
* @param index index of the route to return
* @return the route at index
*/
public RouteInfo getRouteAt(int index) {
return sStatic.mRoutes.get(index);
}
static int getRouteCountStatic() {
return sStatic.mRoutes.size();
}
static RouteInfo getRouteAtStatic(int index) {
return sStatic.mRoutes.get(index);
}
/**
* Create a new user route that may be modified and registered for use by the application.
*
* @param category The category the new route will belong to
* @return A new UserRouteInfo for use by the application
*
* @see #addUserRoute(UserRouteInfo)
* @see #removeUserRoute(UserRouteInfo)
* @see #createRouteCategory(CharSequence)
*/
public UserRouteInfo createUserRoute(RouteCategory category) {
return new UserRouteInfo(category);
}
/**
* Create a new route category. Each route must belong to a category.
*
* @param name Name of the new category
* @param isGroupable true if routes in this category may be grouped with one another
* @return the new RouteCategory
*/
public RouteCategory createRouteCategory(CharSequence name, boolean isGroupable) {
return new RouteCategory(name, ROUTE_TYPE_USER, isGroupable);
}
static void updateRoute(final RouteInfo info) {
dispatchRouteChanged(info);
}
static void dispatchRouteSelected(int type, RouteInfo info) {
final int count = sStatic.mCallbacks.size();
for (int i = 0; i < count; i++) {
final CallbackInfo cbi = sStatic.mCallbacks.get(i);
if ((cbi.type & type) != 0) {
cbi.cb.onRouteSelected(cbi.router, type, info);
}
}
}
static void dispatchRouteUnselected(int type, RouteInfo info) {
final int count = sStatic.mCallbacks.size();
for (int i = 0; i < count; i++) {
final CallbackInfo cbi = sStatic.mCallbacks.get(i);
if ((cbi.type & type) != 0) {
cbi.cb.onRouteUnselected(cbi.router, type, info);
}
}
}
static void dispatchRouteChanged(RouteInfo info) {
final int count = sStatic.mCallbacks.size();
for (int i = 0; i < count; i++) {
final CallbackInfo cbi = sStatic.mCallbacks.get(i);
if ((cbi.type & info.mSupportedTypes) != 0) {
cbi.cb.onRouteChanged(cbi.router, info);
}
}
}
static void dispatchRouteAdded(RouteInfo info) {
final int count = sStatic.mCallbacks.size();
for (int i = 0; i < count; i++) {
final CallbackInfo cbi = sStatic.mCallbacks.get(i);
if ((cbi.type & info.mSupportedTypes) != 0) {
cbi.cb.onRouteAdded(cbi.router, info);
}
}
}
static void dispatchRouteRemoved(RouteInfo info) {
final int count = sStatic.mCallbacks.size();
for (int i = 0; i < count; i++) {
final CallbackInfo cbi = sStatic.mCallbacks.get(i);
if ((cbi.type & info.mSupportedTypes) != 0) {
cbi.cb.onRouteRemoved(cbi.router, info);
}
}
}
static void dispatchRouteGrouped(RouteInfo info, RouteGroup group, int index) {
final int count = sStatic.mCallbacks.size();
for (int i = 0; i < count; i++) {
final CallbackInfo cbi = sStatic.mCallbacks.get(i);
if ((cbi.type & group.mSupportedTypes) != 0) {
cbi.cb.onRouteGrouped(cbi.router, info, group, index);
}
}
}
static void dispatchRouteUngrouped(RouteInfo info, RouteGroup group) {
final int count = sStatic.mCallbacks.size();
for (int i = 0; i < count; i++) {
final CallbackInfo cbi = sStatic.mCallbacks.get(i);
if ((cbi.type & group.mSupportedTypes) != 0) {
cbi.cb.onRouteUngrouped(cbi.router, info, group);
}
}
}
static void onA2dpDeviceConnected() {
final RouteInfo info = new RouteInfo(sStatic.mSystemCategory);
info.mName = "Bluetooth"; // TODO Fetch the real name of the device
sStatic.mBluetoothA2dpRoute = info;
addRoute(sStatic.mBluetoothA2dpRoute);
}
static void onA2dpDeviceDisconnected() {
removeRoute(sStatic.mBluetoothA2dpRoute);
sStatic.mBluetoothA2dpRoute = null;
}
/**
* Information about a media route.
*/
public static class RouteInfo {
CharSequence mName;
private CharSequence mStatus;
int mSupportedTypes;
RouteGroup mGroup;
final RouteCategory mCategory;
Drawable mIcon;
RouteInfo(RouteCategory category) {
mCategory = category;
}
/**
* @return The user-friendly name of a media route. This is the string presented
* to users who may select this as the active route.
*/
public CharSequence getName() {
return mName;
}
/**
* @return The user-friendly status for a media route. This may include a description
* of the currently playing media, if available.
*/
public CharSequence getStatus() {
return mStatus;
}
/**
* @return A media type flag set describing which types this route supports.
*/
public int getSupportedTypes() {
return mSupportedTypes;
}
/**
* @return The group that this route belongs to.
*/
public RouteGroup getGroup() {
return mGroup;
}
/**
* @return the category this route belongs to.
*/
public RouteCategory getCategory() {
return mCategory;
}
/**
* Get the icon representing this route.
* This icon will be used in picker UIs if available.
*
* @return the icon representing this route or null if no icon is available
*/
public Drawable getIconDrawable() {
return mIcon;
}
void setStatusInt(CharSequence status) {
if (!status.equals(mStatus)) {
mStatus = status;
routeUpdated();
if (mGroup != null) {
mGroup.memberStatusChanged(this, status);
}
routeUpdated();
}
}
void routeUpdated() {
updateRoute(this);
}
@Override
public String toString() {
String supportedTypes = typesToString(mSupportedTypes);
return "RouteInfo{ name=" + mName + ", status=" + mStatus +
" category=" + mCategory +
" supportedTypes=" + supportedTypes + "}";
}
}
/**
* Information about a route that the application may define and modify.
*
* @see MediaRouter.RouteInfo
*/
public static class UserRouteInfo extends RouteInfo {
RemoteControlClient mRcc;
UserRouteInfo(RouteCategory category) {
super(category);
mSupportedTypes = ROUTE_TYPE_USER;
}
/**
* Set the user-visible name of this route.
* @param name Name to display to the user to describe this route
*/
public void setName(CharSequence name) {
mName = name;
routeUpdated();
}
/**
* Set the current user-visible status for this route.
* @param status Status to display to the user to describe what the endpoint
* of this route is currently doing
*/
public void setStatus(CharSequence status) {
setStatusInt(status);
}
/**
* Set the RemoteControlClient responsible for reporting playback info for this
* user route.
*
* If this route manages remote playback, the data exposed by this
* RemoteControlClient will be used to reflect and update information
* such as route volume info in related UIs.
*
* @param rcc RemoteControlClient associated with this route
*/
public void setRemoteControlClient(RemoteControlClient rcc) {
mRcc = rcc;
}
/**
* Set an icon that will be used to represent this route.
* The system may use this icon in picker UIs or similar.
*
* @param icon icon drawable to use to represent this route
*/
public void setIconDrawable(Drawable icon) {
mIcon = icon;
}
/**
* Set an icon that will be used to represent this route.
* The system may use this icon in picker UIs or similar.
*
* @param icon Resource ID of an icon drawable to use to represent this route
*/
public void setIconResource(int resId) {
setIconDrawable(sStatic.mResources.getDrawable(resId));
}
}
/**
* Information about a route that consists of multiple other routes in a group.
*/
public static class RouteGroup extends RouteInfo {
final ArrayList mRoutes = new ArrayList();
private boolean mUpdateName;
RouteGroup(RouteCategory category) {
super(category);
mGroup = this;
}
public CharSequence getName() {
if (mUpdateName) updateName();
return super.getName();
}
/**
* Add a route to this group. The route must not currently belong to another group.
*
* @param route route to add to this group
*/
public void addRoute(RouteInfo route) {
if (route.getGroup() != null) {
throw new IllegalStateException("Route " + route + " is already part of a group.");
}
if (route.getCategory() != mCategory) {
throw new IllegalArgumentException(
"Route cannot be added to a group with a different category. " +
"(Route category=" + route.getCategory() +
" group category=" + mCategory + ")");
}
final int at = mRoutes.size();
mRoutes.add(route);
mUpdateName = true;
dispatchRouteGrouped(route, this, at);
routeUpdated();
}
/**
* Add a route to this group before the specified index.
*
* @param route route to add
* @param insertAt insert the new route before this index
*/
public void addRoute(RouteInfo route, int insertAt) {
if (route.getGroup() != null) {
throw new IllegalStateException("Route " + route + " is already part of a group.");
}
if (route.getCategory() != mCategory) {
throw new IllegalArgumentException(
"Route cannot be added to a group with a different category. " +
"(Route category=" + route.getCategory() +
" group category=" + mCategory + ")");
}
mRoutes.add(insertAt, route);
mUpdateName = true;
dispatchRouteGrouped(route, this, insertAt);
routeUpdated();
}
/**
* Remove a route from this group.
*
* @param route route to remove
*/
public void removeRoute(RouteInfo route) {
if (route.getGroup() != this) {
throw new IllegalArgumentException("Route " + route +
" is not a member of this group.");
}
mRoutes.remove(route);
mUpdateName = true;
dispatchRouteUngrouped(route, this);
routeUpdated();
}
/**
* Remove the route at the specified index from this group.
*
* @param index index of the route to remove
*/
public void removeRoute(int index) {
RouteInfo route = mRoutes.remove(index);
mUpdateName = true;
dispatchRouteUngrouped(route, this);
routeUpdated();
}
/**
* @return The number of routes in this group
*/
public int getRouteCount() {
return mRoutes.size();
}
/**
* Return the route in this group at the specified index
*
* @param index Index to fetch
* @return The route at index
*/
public RouteInfo getRouteAt(int index) {
return mRoutes.get(index);
}
/**
* Set an icon that will be used to represent this group.
* The system may use this icon in picker UIs or similar.
*
* @param icon icon drawable to use to represent this group
*/
public void setIconDrawable(Drawable icon) {
mIcon = icon;
}
/**
* Set an icon that will be used to represent this group.
* The system may use this icon in picker UIs or similar.
*
* @param icon Resource ID of an icon drawable to use to represent this group
*/
public void setIconResource(int resId) {
setIconDrawable(sStatic.mResources.getDrawable(resId));
}
void memberNameChanged(RouteInfo info, CharSequence name) {
mUpdateName = true;
routeUpdated();
}
void memberStatusChanged(RouteInfo info, CharSequence status) {
setStatusInt(status);
}
void updateName() {
final StringBuilder sb = new StringBuilder();
final int count = mRoutes.size();
for (int i = 0; i < count; i++) {
final RouteInfo info = mRoutes.get(i);
if (i > 0) sb.append(", ");
sb.append(info.mName);
}
mName = sb.toString();
mUpdateName = false;
}
}
/**
* Definition of a category of routes. All routes belong to a category.
*/
public static class RouteCategory {
CharSequence mName;
int mTypes;
final boolean mGroupable;
RouteCategory(CharSequence name, int types, boolean groupable) {
mName = name;
mTypes = types;
mGroupable = groupable;
}
/**
* @return the name of this route category
*/
public CharSequence getName() {
return mName;
}
/**
* Return the current list of routes in this category that have been added
* to the MediaRouter.
*
* This list will not include routes that are nested within RouteGroups.
* A RouteGroup is treated as a single route within its category.
*
* @param out a List to fill with the routes in this category. If this parameter is
* non-null, it will be cleared, filled with the current routes with this
* category, and returned. If this parameter is null, a new List will be
* allocated to report the category's current routes.
* @return A list with the routes in this category that have been added to the MediaRouter.
*/
public List getRoutes(List out) {
if (out == null) {
out = new ArrayList();
} else {
out.clear();
}
final int count = getRouteCountStatic();
for (int i = 0; i < count; i++) {
final RouteInfo route = getRouteAtStatic(i);
if (route.mCategory == this) {
out.add(route);
}
}
return out;
}
/**
* @return Flag set describing the route types supported by this category
*/
public int getSupportedTypes() {
return mTypes;
}
/**
* Return whether or not this category supports grouping.
*
* If this method returns true, all routes obtained from this category
* via calls to {@link #getRouteAt(int)} will be {@link MediaRouter.RouteGroup}s.
*
* @return true if this category supports
*/
public boolean isGroupable() {
return mGroupable;
}
public String toString() {
return "RouteCategory{ name=" + mName + " types=" + typesToString(mTypes) +
" groupable=" + mGroupable + " routes=" + sStatic.mRoutes.size() + " }";
}
}
static class CallbackInfo {
public int type;
public final Callback cb;
public final MediaRouter router;
public CallbackInfo(Callback cb, int type, MediaRouter router) {
this.cb = cb;
this.type = type;
this.router = router;
}
}
/**
* Interface for receiving events about media routing changes.
* All methods of this interface will be called from the application's main thread.
*
* A Callback will only receive events relevant to routes that the callback
* was registered for.
*
* @see MediaRouter#addCallback(int, Callback)
* @see MediaRouter#removeCallback(Callback)
*/
public interface Callback {
/**
* Called when the supplied route becomes selected as the active route
* for the given route type.
*
* @param router the MediaRouter reporting the event
* @param type Type flag set indicating the routes that have been selected
* @param info Route that has been selected for the given route types
*/
public void onRouteSelected(MediaRouter router, int type, RouteInfo info);
/**
* Called when the supplied route becomes unselected as the active route
* for the given route type.
*
* @param router the MediaRouter reporting the event
* @param type Type flag set indicating the routes that have been unselected
* @param info Route that has been unselected for the given route types
*/
public void onRouteUnselected(MediaRouter router, int type, RouteInfo info);
/**
* Called when a route for the specified type was added.
*
* @param router the MediaRouter reporting the event
* @param info Route that has become available for use
*/
public void onRouteAdded(MediaRouter router, RouteInfo info);
/**
* Called when a route for the specified type was removed.
*
* @param router the MediaRouter reporting the event
* @param info Route that has been removed from availability
*/
public void onRouteRemoved(MediaRouter router, RouteInfo info);
/**
* Called when an aspect of the indicated route has changed.
*
* This will not indicate that the types supported by this route have
* changed, only that cosmetic info such as name or status have been updated.
*
* @param router the MediaRouter reporting the event
* @param info The route that was changed
*/
public void onRouteChanged(MediaRouter router, RouteInfo info);
/**
* Called when a route is added to a group.
*
* @param router the MediaRouter reporting the event
* @param info The route that was added
* @param group The group the route was added to
* @param index The route index within group that info was added at
*/
public void onRouteGrouped(MediaRouter router, RouteInfo info, RouteGroup group, int index);
/**
* Called when a route is removed from a group.
*
* @param router the MediaRouter reporting the event
* @param info The route that was removed
* @param group The group the route was removed from
*/
public void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group);
}
/**
* Stub implementation of the {@link MediaRouter.Callback} interface.
* Each interface method is defined as a no-op. Override just the ones
* you need.
*/
public static class SimpleCallback implements Callback {
@Override
public void onRouteSelected(MediaRouter router, int type, RouteInfo info) {
}
@Override
public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) {
}
@Override
public void onRouteAdded(MediaRouter router, RouteInfo info) {
}
@Override
public void onRouteRemoved(MediaRouter router, RouteInfo info) {
}
@Override
public void onRouteChanged(MediaRouter router, RouteInfo info) {
}
@Override
public void onRouteGrouped(MediaRouter router, RouteInfo info, RouteGroup group,
int index) {
}
@Override
public void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group) {
}
}
class BtChangedBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED.equals(action)) {
final int state = intent.getIntExtra(BluetoothA2dp.EXTRA_STATE, -1);
if (state == BluetoothA2dp.STATE_CONNECTED) {
onA2dpDeviceConnected();
} else if (state == BluetoothA2dp.STATE_DISCONNECTING ||
state == BluetoothA2dp.STATE_DISCONNECTED) {
onA2dpDeviceDisconnected();
}
}
}
}
static class HeadphoneChangedBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (Intent.ACTION_HEADSET_PLUG.equals(action)) {
final boolean plugged = intent.getIntExtra("state", 0) != 0;
final String name = sStatic.mResources.getString(
com.android.internal.R.string.default_audio_route_name_headphones);
onHeadphonesPlugged(plugged, name);
} else if (Intent.ACTION_ANALOG_AUDIO_DOCK_PLUG.equals(action) ||
Intent.ACTION_DIGITAL_AUDIO_DOCK_PLUG.equals(action)) {
final boolean plugged = intent.getIntExtra("state", 0) != 0;
final String name = sStatic.mResources.getString(
com.android.internal.R.string.default_audio_route_name_dock_speakers);
onHeadphonesPlugged(plugged, name);
} else if (Intent.ACTION_HDMI_AUDIO_PLUG.equals(action)) {
final boolean plugged = intent.getIntExtra("state", 0) != 0;
final String name = sStatic.mResources.getString(
com.android.internal.R.string.default_audio_route_name_hdmi);
onHeadphonesPlugged(plugged, name);
}
}
}
}