Merge "Add MediaRouter API." into jb-dev

This commit is contained in:
Adam Powell
2012-06-03 14:11:43 -07:00
committed by Android (Google) Code Review
6 changed files with 992 additions and 17 deletions

View File

@@ -97,6 +97,7 @@ package android {
field public static final java.lang.String RECORD_AUDIO = "android.permission.RECORD_AUDIO";
field public static final java.lang.String REORDER_TASKS = "android.permission.REORDER_TASKS";
field public static final deprecated java.lang.String RESTART_PACKAGES = "android.permission.RESTART_PACKAGES";
field public static final java.lang.String ROUTE_MEDIA_OUTPUT = "android.permission.ROUTE_MEDIA_OUTPUT";
field public static final java.lang.String SEND_SMS = "android.permission.SEND_SMS";
field public static final java.lang.String SET_ACTIVITY_WATCHER = "android.permission.SET_ACTIVITY_WATCHER";
field public static final java.lang.String SET_ALARM = "com.android.alarm.permission.SET_ALARM";
@@ -11486,6 +11487,71 @@ package android.media {
field public static final int DEFAULT = 0; // 0x0
}
public class MediaRouter {
method public void addCallback(int, android.media.MediaRouter.Callback);
method public void addUserRoute(android.media.MediaRouter.UserRouteInfo);
method public android.media.MediaRouter.RouteCategory createRouteCategory(java.lang.CharSequence, boolean);
method public android.media.MediaRouter.UserRouteInfo createUserRoute(android.media.MediaRouter.RouteCategory);
method public static android.media.MediaRouter forApplication(android.content.Context);
method public android.media.MediaRouter.RouteCategory getCategoryAt(int);
method public int getCategoryCount();
method public android.media.MediaRouter.RouteInfo getRouteAt(int);
method public int getRouteCount();
method public void removeCallback(android.media.MediaRouter.Callback);
method public void removeUserRoute(android.media.MediaRouter.UserRouteInfo);
method public void selectRoute(int, android.media.MediaRouter.RouteInfo);
method public void setRouteVolume(int, float);
field public static final int ROUTE_TYPE_LIVE_AUDIO = 1; // 0x1
field public static final int ROUTE_TYPE_USER = 8388608; // 0x800000
}
public static abstract interface MediaRouter.Callback {
method public abstract void onRouteAdded(int, android.media.MediaRouter.RouteInfo);
method public abstract void onRouteChanged(android.media.MediaRouter.RouteInfo);
method public abstract void onRouteRemoved(int, android.media.MediaRouter.RouteInfo);
method public abstract void onRouteSelected(int, android.media.MediaRouter.RouteInfo);
method public abstract void onRouteUnselected(int, android.media.MediaRouter.RouteInfo);
method public abstract void onVolumeChanged(int, float);
}
public class MediaRouter.RouteCategory {
method public java.lang.CharSequence getName();
method public android.media.MediaRouter.RouteInfo getRouteAt(int);
method public int getRouteCount();
method public int getSupportedTypes();
method public boolean isGroupable();
}
public class MediaRouter.RouteGroup extends android.media.MediaRouter.RouteInfo {
method public void addRoute(android.media.MediaRouter.RouteInfo);
method public void addRoute(android.media.MediaRouter.RouteInfo, int);
method public void removeRoute(android.media.MediaRouter.RouteInfo);
method public void removeRoute(int);
}
public class MediaRouter.RouteInfo {
method public android.media.MediaRouter.RouteCategory getCategory();
method public android.media.MediaRouter.RouteGroup getGroup();
method public java.lang.CharSequence getName();
method public java.lang.CharSequence getStatus();
method public int getSupportedTypes();
}
public static class MediaRouter.SimpleCallback implements android.media.MediaRouter.Callback {
ctor public MediaRouter.SimpleCallback();
method public void onRouteAdded(int, android.media.MediaRouter.RouteInfo);
method public void onRouteChanged(android.media.MediaRouter.RouteInfo);
method public void onRouteRemoved(int, android.media.MediaRouter.RouteInfo);
method public void onRouteSelected(int, android.media.MediaRouter.RouteInfo);
method public void onRouteUnselected(int, android.media.MediaRouter.RouteInfo);
method public void onVolumeChanged(int, float);
}
public class MediaRouter.UserRouteInfo extends android.media.MediaRouter.RouteInfo {
method public void setName(java.lang.CharSequence);
method public void setStatus(java.lang.CharSequence);
}
public class MediaScannerConnection implements android.content.ServiceConnection {
ctor public MediaScannerConnection(android.content.Context, android.media.MediaScannerConnection.MediaScannerConnectionClient);
method public void connect();

View File

@@ -19,28 +19,25 @@ package android.view;
import android.content.Context;
/**
* This class is a mediator for accomplishing a given task, for example sharing a file.
* It is responsible for creating a view that performs an action that accomplishes the task.
* This class also implements other functions such a performing a default action.
* <p>
* An ActionProvider can be optionally specified for a {@link MenuItem} and in such a
* case it will be responsible for creating the action view that appears in the
* {@link android.app.ActionBar} as a substitute for the menu item when the item is
* displayed as an action item. Also the provider is responsible for performing a
* default action if a menu item placed on the overflow menu of the ActionBar is
* selected and none of the menu item callbacks has handled the selection. For this
* case the provider can also optionally provide a sub-menu for accomplishing the
* task at hand.
* </p>
* <p>
* There are two ways for using an action provider for creating and handling of action views:
* An ActionProvider defines rich menu interaction in a single component.
* ActionProvider can generate action views for use in the action bar,
* dynamically populate submenus of a MenuItem, and handle default menu
* item invocations.
*
* <p>An ActionProvider can be optionally specified for a {@link MenuItem} and will be
* responsible for creating the action view that appears in the {@link android.app.ActionBar}
* in place of a simple button in the bar. When the menu item is presented in a way that
* does not allow custom action views, (e.g. in an overflow menu,) the ActionProvider
* can perform a default action.</p>
*
* <p>There are two ways to use an action provider:
* <ul>
* <li>
* Setting the action provider on a {@link MenuItem} directly by calling
* Set the action provider on a {@link MenuItem} directly by calling
* {@link MenuItem#setActionProvider(ActionProvider)}.
* </li>
* <li>
* Declaring the action provider in the menu XML resource. For example:
* Declare the action provider in an XML menu resource. For example:
* <pre>
* <code>
* &lt;item android:id="@+id/my_menu_item"

View File

@@ -636,6 +636,12 @@
android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
android:protectionLevel="signature" />
<!-- Allows an application to route media output to other devices. -->
<permission android:name="android.permission.ROUTE_MEDIA_OUTPUT"
android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
android:label="@string/permlab_route_media_output"
android:description="@string/permdesc_route_media_output" />
<!-- =========================================== -->
<!-- Permissions associated with telephony state -->
<!-- =========================================== -->

View File

@@ -881,6 +881,11 @@
<java-symbol type="string" name="granularity_label_word" />
<java-symbol type="string" name="granularity_label_link" />
<java-symbol type="string" name="granularity_label_line" />
<java-symbol type="string" name="default_audio_route_name" />
<java-symbol type="string" name="default_audio_route_name_headphones" />
<java-symbol type="string" name="default_audio_route_name_dock_speakers" />
<java-symbol type="string" name="default_audio_route_name_hdmi" />
<java-symbol type="string" name="default_audio_route_category_name" />
<java-symbol type="plurals" name="abbrev_in_num_days" />
<java-symbol type="plurals" name="abbrev_in_num_hours" />

View File

@@ -3149,6 +3149,11 @@
<!-- Description of an application permission, used to invoke default container service to copy content. -->
<string name="permdesc_copyProtectedData">Allows the app to invoke default container service to copy content. Not for use by normal apps.</string>
<!-- Title of an application permission that lets an application route media output. -->
<string name="permlab_route_media_output">Route media output</string>
<!-- Description of an application permission that lets an application route media output. -->
<string name="permdesc_route_media_output">Allows an application to route media output to other external devices.</string>
<!-- Shown in the tutorial for tap twice for zoom control. -->
<string name="tutorial_double_tap_to_zoom_message_short">Touch twice for zoom control</string>
@@ -3563,4 +3568,25 @@
from the activity resolver to use just this once. [CHAR LIMIT=25] -->
<string name="activity_resolver_use_once">Just once</string>
<!-- Name of the default audio route when nothing is connected to
a headphone or other wired audio output jack. [CHAR LIMIT=25] -->
<string name="default_audio_route_name">Phone speaker</string>
<!-- Name of the default audio route for tablets when nothing
is connected to a headphone or other wired audio output jack. [CHAR LIMIT=25] -->
<string name="default_audio_route_name" product="tablet">Tablet speakers</string>
<!-- Name of the default audio route when wired headphones are
connected. [CHAR LIMIT=25] -->
<string name="default_audio_route_name_headphones">Headphones</string>
<!-- Name of the default audio route when an audio dock is connected. [CHAR LIMIT=25] -->
<string name="default_audio_route_name_dock_speakers">Dock speakers</string>
<!-- Name of the default audio route when HDMI is connected. [CHAR LIMIT=25] -->
<string name="default_audio_route_name_hdmi">HDMI audio</string>
<!-- Name of the default audio route category. [CHAR LIMIT=25] -->
<string name="default_audio_route_category_name">System</string>
</resources>

View File

@@ -0,0 +1,875 @@
/*
* 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.os.Handler;
import android.util.Log;
import java.util.ArrayList;
import java.util.HashMap;
/**
* MediaRouter allows applications to control the routing of media channels
* and streams from the current device to external speakers and destination devices.
*
* <p>Media routes should only be modified on your application's main thread.</p>
*/
public class MediaRouter {
private static final String TAG = "MediaRouter";
private Context mAppContext;
private AudioManager mAudioManager;
private Handler mHandler;
private final ArrayList<CallbackInfo> mCallbacks = new ArrayList<CallbackInfo>();
private final ArrayList<RouteInfo> mRoutes = new ArrayList<RouteInfo>();
private final ArrayList<RouteCategory> mCategories = new ArrayList<RouteCategory>();
private final RouteCategory mSystemCategory;
private RouteInfo mDefaultAudio;
private RouteInfo mBluetoothA2dpRoute;
private RouteInfo mSelectedRoute;
// These get removed when an activity dies
final ArrayList<BroadcastReceiver> mRegisteredReceivers = new ArrayList<BroadcastReceiver>();
/**
* Route type flag for live audio.
*
* <p>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.</p>
*
* <p>Once initiated this routing is transparent to the application. All audio
* played on the media stream will be routed to the selected destination.</p>
*/
public static final int ROUTE_TYPE_LIVE_AUDIO = 0x1;
/**
* Route type flag for application-specific usage.
*
* <p>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.</p>
*/
public static final int ROUTE_TYPE_USER = 0x00800000;
// Maps application contexts
static final HashMap<Context, MediaRouter> sRouters = new HashMap<Context, MediaRouter>();
/**
* Return a MediaRouter for the application that the specified Context belongs to.
* The behavior or availability of media routing may depend on
* various parameters of the context.
*
* @param context Context for the desired router
* @return Router for the supplied Context
*/
public static MediaRouter forApplication(Context context) {
final Context appContext = context.getApplicationContext();
if (!sRouters.containsKey(appContext)) {
final MediaRouter r = new MediaRouter(appContext);
sRouters.put(appContext, r);
return r;
} else {
return sRouters.get(appContext);
}
}
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();
}
private MediaRouter(Context context) {
mAppContext = context;
mHandler = new Handler(mAppContext.getMainLooper());
mAudioManager = (AudioManager) mAppContext.getSystemService(Context.AUDIO_SERVICE);
mSystemCategory = new RouteCategory(mAppContext.getText(
com.android.internal.R.string.default_audio_route_category_name),
ROUTE_TYPE_LIVE_AUDIO, false);
registerReceivers();
createDefaultRoutes();
}
private void registerReceivers() {
final BroadcastReceiver volumeReceiver = new VolumeChangedBroadcastReceiver();
mAppContext.registerReceiver(volumeReceiver,
new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION));
mRegisteredReceivers.add(volumeReceiver);
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);
final BroadcastReceiver plugReceiver = new HeadphoneChangedBroadcastReceiver();
mAppContext.registerReceiver(plugReceiver, speakerFilter);
mRegisteredReceivers.add(plugReceiver);
}
void unregisterReceivers() {
final int count = mRegisteredReceivers.size();
for (int i = 0; i < count; i++) {
final BroadcastReceiver r = mRegisteredReceivers.get(i);
mAppContext.unregisterReceiver(r);
}
mRegisteredReceivers.clear();
}
private void createDefaultRoutes() {
mDefaultAudio = new RouteInfo(mSystemCategory);
mDefaultAudio.mName = mAppContext.getText(
com.android.internal.R.string.default_audio_route_name);
mDefaultAudio.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO;
addRoute(mDefaultAudio);
}
void onHeadphonesPlugged(boolean headphonesPresent, String headphonesName) {
mDefaultAudio.mName = headphonesPresent ? headphonesName : mAppContext.getText(
com.android.internal.R.string.default_audio_route_name);
dispatchRouteChanged(mDefaultAudio);
}
/**
* Set volume for the specified route types.
*
* @param types Volume will be set for these route types
* @param volume Volume to set in the range 0.f (inaudible) to 1.f (full volume).
*/
public void setRouteVolume(int types, float volume) {
if ((types & ROUTE_TYPE_LIVE_AUDIO) != 0) {
final int index = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, index, 0);
}
if ((types & ROUTE_TYPE_USER) != 0) {
dispatchVolumeChanged(ROUTE_TYPE_USER, volume);
}
}
/**
* 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 = mCallbacks.size();
for (int i = 0; i < count; i++) {
final CallbackInfo info = mCallbacks.get(i);
if (info.cb == cb) {
info.type &= types;
return;
}
}
mCallbacks.add(new CallbackInfo(cb, types));
}
/**
* 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 = mCallbacks.size();
for (int i = 0; i < count; i++) {
if (mCallbacks.get(i).cb == cb) {
mCallbacks.remove(i);
return;
}
}
Log.w(TAG, "removeCallback(" + cb + "): callback not registered");
}
public void selectRoute(int types, RouteInfo route) {
if (mSelectedRoute == route) return;
if (mSelectedRoute != null) {
// TODO filter types properly
dispatchRouteUnselected(types & mSelectedRoute.getSupportedTypes(), mSelectedRoute);
}
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);
}
void addRoute(RouteInfo info) {
final RouteCategory cat = info.getCategory();
if (!mCategories.contains(cat)) {
mCategories.add(cat);
}
if (info.getCategory().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());
group.addRoute(info);
info = group;
}
final boolean onlyRoute = mRoutes.isEmpty();
mRoutes.add(info);
dispatchRouteAdded(info);
if (onlyRoute) {
selectRoute(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);
}
void removeRoute(RouteInfo info) {
if (mRoutes.remove(info)) {
final RouteCategory removingCat = info.getCategory();
final int count = mRoutes.size();
boolean found = false;
for (int i = 0; i < count; i++) {
final RouteCategory cat = mRoutes.get(i).getCategory();
if (removingCat == cat) {
found = true;
break;
}
}
if (!found) {
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 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 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 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 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);
}
void updateRoute(final RouteInfo info) {
dispatchRouteChanged(info);
}
void dispatchRouteSelected(int type, RouteInfo info) {
final int count = mCallbacks.size();
for (int i = 0; i < count; i++) {
final CallbackInfo cbi = mCallbacks.get(i);
if ((cbi.type & type) != 0) {
cbi.cb.onRouteSelected(type, info);
}
}
}
void dispatchRouteUnselected(int type, RouteInfo info) {
final int count = mCallbacks.size();
for (int i = 0; i < count; i++) {
final CallbackInfo cbi = mCallbacks.get(i);
if ((cbi.type & type) != 0) {
cbi.cb.onRouteUnselected(type, info);
}
}
}
void dispatchRouteChanged(RouteInfo info) {
final int count = mCallbacks.size();
for (int i = 0; i < count; i++) {
final CallbackInfo cbi = mCallbacks.get(i);
if ((cbi.type & info.mSupportedTypes) != 0) {
cbi.cb.onRouteChanged(info);
}
}
}
void dispatchRouteAdded(RouteInfo info) {
final int count = mCallbacks.size();
for (int i = 0; i < count; i++) {
final CallbackInfo cbi = mCallbacks.get(i);
if ((cbi.type & info.mSupportedTypes) != 0) {
cbi.cb.onRouteAdded(info.mSupportedTypes, info);
}
}
}
void dispatchRouteRemoved(RouteInfo info) {
final int count = mCallbacks.size();
for (int i = 0; i < count; i++) {
final CallbackInfo cbi = mCallbacks.get(i);
if ((cbi.type & info.mSupportedTypes) != 0) {
cbi.cb.onRouteRemoved(info.mSupportedTypes, info);
}
}
}
void dispatchVolumeChanged(int type, float volume) {
final int count = mCallbacks.size();
for (int i = 0; i < count; i++) {
final CallbackInfo cbi = mCallbacks.get(i);
if ((cbi.type & type) != 0) {
cbi.cb.onVolumeChanged(type, volume);
}
}
}
void onA2dpDeviceConnected() {
final RouteInfo info = new RouteInfo(mSystemCategory);
info.mName = "Bluetooth"; // TODO Fetch the real name of the device
mBluetoothA2dpRoute = info;
addRoute(mBluetoothA2dpRoute);
}
void onA2dpDeviceDisconnected() {
removeRoute(mBluetoothA2dpRoute);
mBluetoothA2dpRoute = null;
}
/**
* Information about a media route.
*/
public class RouteInfo {
CharSequence mName;
private CharSequence mStatus;
int mSupportedTypes;
RouteGroup mGroup;
final RouteCategory mCategory;
RouteInfo(RouteCategory category) {
mCategory = category;
category.mRoutes.add(this);
}
/**
* @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;
}
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 class UserRouteInfo extends RouteInfo {
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);
}
}
/**
* Information about a route that consists of multiple other routes in a group.
*/
public class RouteGroup extends RouteInfo {
final ArrayList<RouteInfo> mRoutes = new ArrayList<RouteInfo>();
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 + ")");
}
mRoutes.add(route);
mUpdateName = true;
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;
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;
routeUpdated();
}
/**
* Remove the route at the specified index from this group.
*
* @param index index of the route to remove
*/
public void removeRoute(int index) {
mRoutes.remove(index);
mUpdateName = true;
routeUpdated();
}
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 class RouteCategory {
final ArrayList<RouteInfo> mRoutes = new ArrayList<RouteInfo>();
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 number of routes in this category
*/
public int getRouteCount() {
return mRoutes.size();
}
/**
* Return a route from this category
*
* @param index Index from [0-getRouteCount)
* @return the route at the given index
*/
public RouteInfo getRouteAt(int index) {
return mRoutes.get(index);
}
/**
* @return Flag set describing the route types supported by this category
*/
public int getSupportedTypes() {
return mTypes;
}
/**
* Return whether or not this category supports grouping.
*
* <p>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=" + mRoutes.size() + " }";
}
}
static class CallbackInfo {
public int type;
public Callback cb;
public CallbackInfo(Callback cb, int type) {
this.cb = cb;
this.type = type;
}
}
/**
* Interface for receiving events about media routing changes.
* All methods of this interface will be called from the application's main thread.
*
* <p>A Callback will only receive events relevant to routes that the callback
* was registered for.</p>
*
* @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 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(int type, RouteInfo info);
/**
* Called when the supplied route becomes unselected as the active route
* for the given route type.
*
* @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(int type, RouteInfo info);
/**
* Called when the volume is changed for the specified route types.
*
* @param type Type flags indicating which volume type was changed
* @param volume New volume value in the range 0 (inaudible) to 1 (full)
*/
public void onVolumeChanged(int type, float volume);
/**
* Called when a route for the specified type was added.
*
* @param type Type flags indicating which types the added route supports
* @param info Route that has become available for use
*/
public void onRouteAdded(int type, RouteInfo info);
/**
* Called when a route for the specified type was removed.
*
* @param type Type flags indicating which types the removed route supported
* @param info Route that has been removed from availability
*/
public void onRouteRemoved(int type, RouteInfo info);
/**
* Called when an aspect of the indicated route has changed.
*
* <p>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.</p>
*
* @param info The route that was changed
*/
public void onRouteChanged(RouteInfo info);
}
/**
* 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(int type, RouteInfo info) {
}
@Override
public void onRouteUnselected(int type, RouteInfo info) {
}
@Override
public void onVolumeChanged(int type, float volume) {
}
@Override
public void onRouteAdded(int type, RouteInfo info) {
}
@Override
public void onRouteRemoved(int type, RouteInfo info) {
}
@Override
public void onRouteChanged(RouteInfo info) {
}
}
class VolumeChangedBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (AudioManager.VOLUME_CHANGED_ACTION.equals(action) &&
AudioManager.STREAM_MUSIC == intent.getIntExtra(
AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1)) {
final int maxVol = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
final int volExtra = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0);
final float volume = (float) volExtra / maxVol;
dispatchVolumeChanged(ROUTE_TYPE_LIVE_AUDIO, volume);
}
}
}
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();
}
}
}
}
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 = mAppContext.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 = mAppContext.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 = mAppContext.getString(
com.android.internal.R.string.default_audio_route_name_hdmi);
onHeadphonesPlugged(plugged, name);
}
}
}
}