am f7910e31: Merge "Add wireless display selection support to MediaRouter." into jb-mr1-dev

* commit 'f7910e3148cd5ea21c9c3cdb7023c997139264ce':
  Add wireless display selection support to MediaRouter.
This commit is contained in:
Adam Powell
2012-09-28 12:21:52 -07:00
committed by Android Git Automerger
7 changed files with 351 additions and 50 deletions

View File

@@ -11750,6 +11750,7 @@ package android.media {
method public void removeUserRoute(android.media.MediaRouter.UserRouteInfo); method public void removeUserRoute(android.media.MediaRouter.UserRouteInfo);
method public void selectRoute(int, android.media.MediaRouter.RouteInfo); method public void selectRoute(int, android.media.MediaRouter.RouteInfo);
field public static final int ROUTE_TYPE_LIVE_AUDIO = 1; // 0x1 field public static final int ROUTE_TYPE_LIVE_AUDIO = 1; // 0x1
field public static final int ROUTE_TYPE_LIVE_VIDEO = 2; // 0x2
field public static final int ROUTE_TYPE_USER = 8388608; // 0x800000 field public static final int ROUTE_TYPE_USER = 8388608; // 0x800000
} }
@@ -11798,6 +11799,7 @@ package android.media {
method public int getVolume(); method public int getVolume();
method public int getVolumeHandling(); method public int getVolumeHandling();
method public int getVolumeMax(); method public int getVolumeMax();
method public boolean isEnabled();
method public void requestSetVolume(int); method public void requestSetVolume(int);
method public void requestUpdateVolume(int); method public void requestUpdateVolume(int);
method public void setTag(java.lang.Object); method public void setTag(java.lang.Object);

View File

@@ -221,21 +221,28 @@ public class MediaRouteButton extends View {
void updateRouteCount() { void updateRouteCount() {
final int N = mRouter.getRouteCount(); final int N = mRouter.getRouteCount();
int count = 0; int count = 0;
boolean hasVideoRoutes = false;
for (int i = 0; i < N; i++) { for (int i = 0; i < N; i++) {
final RouteInfo route = mRouter.getRouteAt(i); final RouteInfo route = mRouter.getRouteAt(i);
if ((route.getSupportedTypes() & mRouteTypes) != 0) { final int routeTypes = route.getSupportedTypes();
if ((routeTypes & mRouteTypes) != 0) {
if (route instanceof RouteGroup) { if (route instanceof RouteGroup) {
count += ((RouteGroup) route).getRouteCount(); count += ((RouteGroup) route).getRouteCount();
} else { } else {
count++; count++;
} }
if ((routeTypes & MediaRouter.ROUTE_TYPE_LIVE_VIDEO) != 0) {
hasVideoRoutes = true;
}
} }
} }
setEnabled(count != 0); setEnabled(count != 0);
// Only allow toggling if we have more than just user routes // Only allow toggling if we have more than just user routes.
mToggleMode = count == 2 && (mRouteTypes & MediaRouter.ROUTE_TYPE_LIVE_AUDIO) != 0; // Don't toggle if we support video routes, we may have to let the dialog scan.
mToggleMode = count == 2 && (mRouteTypes & MediaRouter.ROUTE_TYPE_LIVE_AUDIO) != 0 &&
!hasVideoRoutes;
} }
@Override @Override

View File

@@ -25,7 +25,7 @@ import android.app.MediaRouteActionProvider;
import android.app.MediaRouteButton; import android.app.MediaRouteButton;
import android.content.Context; import android.content.Context;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.media.AudioManager; import android.hardware.display.DisplayManager;
import android.media.MediaRouter; import android.media.MediaRouter;
import android.media.MediaRouter.RouteCategory; import android.media.MediaRouter.RouteCategory;
import android.media.MediaRouter.RouteGroup; import android.media.MediaRouter.RouteGroup;
@@ -70,6 +70,7 @@ public class MediaRouteChooserDialogFragment extends DialogFragment {
}; };
MediaRouter mRouter; MediaRouter mRouter;
DisplayManager mDisplayService;
private int mRouteTypes; private int mRouteTypes;
private LayoutInflater mInflater; private LayoutInflater mInflater;
@@ -97,6 +98,7 @@ public class MediaRouteChooserDialogFragment extends DialogFragment {
public void onAttach(Activity activity) { public void onAttach(Activity activity) {
super.onAttach(activity); super.onAttach(activity);
mRouter = (MediaRouter) activity.getSystemService(Context.MEDIA_ROUTER_SERVICE); mRouter = (MediaRouter) activity.getSystemService(Context.MEDIA_ROUTER_SERVICE);
mDisplayService = (DisplayManager) activity.getSystemService(Context.DISPLAY_SERVICE);
} }
@Override @Override
@@ -119,6 +121,15 @@ public class MediaRouteChooserDialogFragment extends DialogFragment {
public void setRouteTypes(int types) { public void setRouteTypes(int types) {
mRouteTypes = types; mRouteTypes = types;
if ((mRouteTypes & MediaRouter.ROUTE_TYPE_LIVE_VIDEO) != 0 && mDisplayService == null) {
final Context activity = getActivity();
if (activity != null) {
mDisplayService = (DisplayManager) activity.getSystemService(
Context.DISPLAY_SERVICE);
}
} else {
mDisplayService = null;
}
} }
void updateVolume() { void updateVolume() {
@@ -194,6 +205,9 @@ public class MediaRouteChooserDialogFragment extends DialogFragment {
@Override @Override
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
if (mDisplayService != null) {
mDisplayService.scanWifiDisplays();
}
} }
private static class ViewHolder { private static class ViewHolder {
@@ -253,7 +267,9 @@ public class MediaRouteChooserDialogFragment extends DialogFragment {
final RouteCategory cat = mRouter.getCategoryAt(i); final RouteCategory cat = mRouter.getCategoryAt(i);
routes = cat.getRoutes(mCatRouteList); routes = cat.getRoutes(mCatRouteList);
mItems.add(cat); if (!cat.isSystem()) {
mItems.add(cat);
}
if (cat == mCategoryEditingGroups) { if (cat == mCategoryEditingGroups) {
addGroupEditingCategoryRoutes(routes); addGroupEditingCategoryRoutes(routes);
@@ -370,6 +386,7 @@ public class MediaRouteChooserDialogFragment extends DialogFragment {
public boolean isEnabled(int position) { public boolean isEnabled(int position) {
switch (getItemViewType(position)) { switch (getItemViewType(position)) {
case VIEW_ROUTE: case VIEW_ROUTE:
return ((RouteInfo) mItems.get(position)).isEnabled();
case VIEW_GROUPING_ROUTE: case VIEW_GROUPING_ROUTE:
case VIEW_GROUPING_DONE: case VIEW_GROUPING_DONE:
return true; return true;
@@ -434,6 +451,7 @@ public class MediaRouteChooserDialogFragment extends DialogFragment {
} }
convertView.setActivated(position == mSelectedItemPosition); convertView.setActivated(position == mSelectedItemPosition);
convertView.setEnabled(isEnabled(position));
return convertView; return convertView;
} }

View File

@@ -24,7 +24,8 @@
android:layout_height="56dp" android:layout_height="56dp"
android:scaleType="center" android:scaleType="center"
android:id="@+id/icon" android:id="@+id/icon"
android:visibility="gone" /> android:visibility="gone"
android:duplicateParentState="true" />
<LinearLayout android:layout_width="0dp" <LinearLayout android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="match_parent"
@@ -32,21 +33,24 @@
android:orientation="vertical" android:orientation="vertical"
android:gravity="start|center_vertical" android:gravity="start|center_vertical"
android:paddingStart="?android:attr/listPreferredItemPaddingStart" android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"> android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:duplicateParentState="true">
<TextView android:id="@android:id/text1" <TextView android:id="@android:id/text1"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:singleLine="true" android:singleLine="true"
android:ellipsize="marquee" android:ellipsize="marquee"
android:textAppearance="?android:attr/textAppearanceMedium" /> android:textAppearance="?android:attr/textAppearanceMedium"
android:duplicateParentState="true" />
<TextView android:id="@android:id/text2" <TextView android:id="@android:id/text2"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:singleLine="true" android:singleLine="true"
android:ellipsize="marquee" android:ellipsize="marquee"
android:textAppearance="?android:attr/textAppearanceSmall" /> android:textAppearance="?android:attr/textAppearanceSmall"
android:duplicateParentState="true" />
</LinearLayout> </LinearLayout>
<ImageButton <ImageButton
@@ -56,6 +60,7 @@
android:background="?android:attr/selectableItemBackground" android:background="?android:attr/selectableItemBackground"
android:src="@drawable/ic_media_group_expand" android:src="@drawable/ic_media_group_expand"
android:scaleType="center" android:scaleType="center"
android:visibility="gone" /> android:visibility="gone"
android:duplicateParentState="true" />
</LinearLayout> </LinearLayout>

View File

@@ -3783,6 +3783,18 @@
<!-- Content description of a MediaRouteButton for accessibility support --> <!-- Content description of a MediaRouteButton for accessibility support -->
<string name="media_route_button_content_description">Media output</string> <string name="media_route_button_content_description">Media output</string>
<!-- Status message for remote routes attempting to scan/determine availability -->
<string name="media_route_status_scanning">Scanning...</string>
<!-- Status message for a remote route attempting to connect -->
<string name="media_route_status_connecting">Connecting...</string>
<!-- Status message for a remote route that is confirmed to be available for connection -->
<string name="media_route_status_available">Available</string>
<!-- Status message for remote routes that are not available for connection right now -->
<string name="media_route_status_not_available">Not available</string>
<!-- Display manager service --> <!-- Display manager service -->
<!-- Name of the built-in display. [CHAR LIMIT=50] --> <!-- Name of the built-in display. [CHAR LIMIT=50] -->

View File

@@ -826,6 +826,10 @@
<java-symbol type="string" name="default_audio_route_name_hdmi" /> <java-symbol type="string" name="default_audio_route_name_hdmi" />
<java-symbol type="string" name="default_audio_route_category_name" /> <java-symbol type="string" name="default_audio_route_category_name" />
<java-symbol type="string" name="safe_media_volume_warning" /> <java-symbol type="string" name="safe_media_volume_warning" />
<java-symbol type="string" name="media_route_status_scanning" />
<java-symbol type="string" name="media_route_status_connecting" />
<java-symbol type="string" name="media_route_status_available" />
<java-symbol type="string" name="media_route_status_not_available" />
<java-symbol type="plurals" name="abbrev_in_num_days" /> <java-symbol type="plurals" name="abbrev_in_num_days" />
<java-symbol type="plurals" name="abbrev_in_num_hours" /> <java-symbol type="plurals" name="abbrev_in_num_hours" />

View File

@@ -22,12 +22,17 @@ import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.res.Resources; import android.content.res.Resources;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.hardware.display.DisplayManager;
import android.hardware.display.WifiDisplay;
import android.hardware.display.WifiDisplayStatus;
import android.os.Handler; import android.os.Handler;
import android.os.IBinder; import android.os.IBinder;
import android.os.RemoteException; import android.os.RemoteException;
import android.os.ServiceManager; import android.os.ServiceManager;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.view.Display;
import android.view.DisplayInfo;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
@@ -51,6 +56,7 @@ public class MediaRouter {
static class Static { static class Static {
final Resources mResources; final Resources mResources;
final IAudioService mAudioService; final IAudioService mAudioService;
final DisplayManager mDisplayService;
final Handler mHandler; final Handler mHandler;
final CopyOnWriteArrayList<CallbackInfo> mCallbacks = final CopyOnWriteArrayList<CallbackInfo> mCallbacks =
new CopyOnWriteArrayList<CallbackInfo>(); new CopyOnWriteArrayList<CallbackInfo>();
@@ -60,18 +66,20 @@ public class MediaRouter {
final RouteCategory mSystemCategory; final RouteCategory mSystemCategory;
final AudioRoutesInfo mCurRoutesInfo = new AudioRoutesInfo(); final AudioRoutesInfo mCurAudioRoutesInfo = new AudioRoutesInfo();
RouteInfo mDefaultAudio; RouteInfo mDefaultAudioVideo;
RouteInfo mBluetoothA2dpRoute; RouteInfo mBluetoothA2dpRoute;
RouteInfo mSelectedRoute; RouteInfo mSelectedRoute;
final IAudioRoutesObserver.Stub mRoutesObserver = new IAudioRoutesObserver.Stub() { WifiDisplayStatus mLastKnownWifiDisplayStatus;
final IAudioRoutesObserver.Stub mAudioRoutesObserver = new IAudioRoutesObserver.Stub() {
public void dispatchAudioRoutesChanged(final AudioRoutesInfo newRoutes) { public void dispatchAudioRoutesChanged(final AudioRoutesInfo newRoutes) {
mHandler.post(new Runnable() { mHandler.post(new Runnable() {
@Override public void run() { @Override public void run() {
updateRoutes(newRoutes); updateAudioRoutes(newRoutes);
} }
}); });
} }
@@ -84,34 +92,42 @@ public class MediaRouter {
IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE); IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
mAudioService = IAudioService.Stub.asInterface(b); mAudioService = IAudioService.Stub.asInterface(b);
mDisplayService = (DisplayManager) appContext.getSystemService(Context.DISPLAY_SERVICE);
mSystemCategory = new RouteCategory( mSystemCategory = new RouteCategory(
com.android.internal.R.string.default_audio_route_category_name, com.android.internal.R.string.default_audio_route_category_name,
ROUTE_TYPE_LIVE_AUDIO, false); ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO, false);
mSystemCategory.mIsSystem = true;
} }
// Called after sStatic is initialized // Called after sStatic is initialized
void startMonitoringRoutes(Context appContext) { void startMonitoringRoutes(Context appContext) {
mDefaultAudio = new RouteInfo(mSystemCategory); mDefaultAudioVideo = new RouteInfo(mSystemCategory);
mDefaultAudio.mNameResId = com.android.internal.R.string.default_audio_route_name; mDefaultAudioVideo.mNameResId = com.android.internal.R.string.default_audio_route_name;
mDefaultAudio.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO; mDefaultAudioVideo.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO;
addRoute(mDefaultAudio); addRoute(mDefaultAudioVideo);
appContext.registerReceiver(new VolumeChangeReceiver(), appContext.registerReceiver(new VolumeChangeReceiver(),
new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION)); new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION));
AudioRoutesInfo newRoutes = null; AudioRoutesInfo newAudioRoutes = null;
try { try {
newRoutes = mAudioService.startWatchingRoutes(mRoutesObserver); newAudioRoutes = mAudioService.startWatchingRoutes(mAudioRoutesObserver);
} catch (RemoteException e) { } catch (RemoteException e) {
} }
if (newRoutes != null) { if (newAudioRoutes != null) {
updateRoutes(newRoutes); updateAudioRoutes(newAudioRoutes);
} }
updateWifiDisplayStatus(mDisplayService.getWifiDisplayStatus());
appContext.registerReceiver(new WifiDisplayStatusChangedReceiver(),
new IntentFilter(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED));
} }
void updateRoutes(AudioRoutesInfo newRoutes) { void updateAudioRoutes(AudioRoutesInfo newRoutes) {
if (newRoutes.mMainType != mCurRoutesInfo.mMainType) { if (newRoutes.mMainType != mCurAudioRoutesInfo.mMainType) {
mCurRoutesInfo.mMainType = newRoutes.mMainType; mCurAudioRoutesInfo.mMainType = newRoutes.mMainType;
int name; int name;
if ((newRoutes.mMainType&AudioRoutesInfo.MAIN_HEADPHONES) != 0 if ((newRoutes.mMainType&AudioRoutesInfo.MAIN_HEADPHONES) != 0
|| (newRoutes.mMainType&AudioRoutesInfo.MAIN_HEADSET) != 0) { || (newRoutes.mMainType&AudioRoutesInfo.MAIN_HEADSET) != 0) {
@@ -123,8 +139,8 @@ public class MediaRouter {
} else { } else {
name = com.android.internal.R.string.default_audio_route_name; name = com.android.internal.R.string.default_audio_route_name;
} }
sStatic.mDefaultAudio.mNameResId = name; sStatic.mDefaultAudioVideo.mNameResId = name;
dispatchRouteChanged(sStatic.mDefaultAudio); dispatchRouteChanged(sStatic.mDefaultAudioVideo);
} }
boolean a2dpEnabled; boolean a2dpEnabled;
@@ -135,17 +151,17 @@ public class MediaRouter {
a2dpEnabled = false; a2dpEnabled = false;
} }
if (!TextUtils.equals(newRoutes.mBluetoothName, mCurRoutesInfo.mBluetoothName)) { if (!TextUtils.equals(newRoutes.mBluetoothName, mCurAudioRoutesInfo.mBluetoothName)) {
mCurRoutesInfo.mBluetoothName = newRoutes.mBluetoothName; mCurAudioRoutesInfo.mBluetoothName = newRoutes.mBluetoothName;
if (mCurRoutesInfo.mBluetoothName != null) { if (mCurAudioRoutesInfo.mBluetoothName != null) {
if (sStatic.mBluetoothA2dpRoute == null) { if (sStatic.mBluetoothA2dpRoute == null) {
final RouteInfo info = new RouteInfo(sStatic.mSystemCategory); final RouteInfo info = new RouteInfo(sStatic.mSystemCategory);
info.mName = mCurRoutesInfo.mBluetoothName; info.mName = mCurAudioRoutesInfo.mBluetoothName;
info.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO; info.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO;
sStatic.mBluetoothA2dpRoute = info; sStatic.mBluetoothA2dpRoute = info;
addRoute(sStatic.mBluetoothA2dpRoute); addRoute(sStatic.mBluetoothA2dpRoute);
} else { } else {
sStatic.mBluetoothA2dpRoute.mName = mCurRoutesInfo.mBluetoothName; sStatic.mBluetoothA2dpRoute.mName = mCurAudioRoutesInfo.mBluetoothName;
dispatchRouteChanged(sStatic.mBluetoothA2dpRoute); dispatchRouteChanged(sStatic.mBluetoothA2dpRoute);
} }
} else if (sStatic.mBluetoothA2dpRoute != null) { } else if (sStatic.mBluetoothA2dpRoute != null) {
@@ -155,11 +171,11 @@ public class MediaRouter {
} }
if (mBluetoothA2dpRoute != null) { if (mBluetoothA2dpRoute != null) {
if (mCurRoutesInfo.mMainType != AudioRoutesInfo.MAIN_SPEAKER && if (mCurAudioRoutesInfo.mMainType != AudioRoutesInfo.MAIN_SPEAKER &&
mSelectedRoute == mBluetoothA2dpRoute) { mSelectedRoute == mBluetoothA2dpRoute) {
selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mDefaultAudio); selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mDefaultAudioVideo);
} else if (mCurRoutesInfo.mMainType == AudioRoutesInfo.MAIN_SPEAKER && } else if (mCurAudioRoutesInfo.mMainType == AudioRoutesInfo.MAIN_SPEAKER &&
mSelectedRoute == mDefaultAudio && a2dpEnabled) { mSelectedRoute == mDefaultAudioVideo && a2dpEnabled) {
selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mBluetoothA2dpRoute); selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO, mBluetoothA2dpRoute);
} }
} }
@@ -180,6 +196,20 @@ public class MediaRouter {
*/ */
public static final int ROUTE_TYPE_LIVE_AUDIO = 0x1; public static final int ROUTE_TYPE_LIVE_AUDIO = 0x1;
/**
* Route type flag for live video.
*
* <p>A device that supports live video routing will allow a mirrored version
* of the device's primary display or a customized
* {@link android.app.Presentation Presentation} to be routed to supported destinations.</p>
*
* <p>Once initiated, display mirroring is transparent to the application.
* While remote routing is active the application may use a
* {@link android.app.Presentation Presentation} to replace the mirrored view
* on the external display with different content.</p>
*/
public static final int ROUTE_TYPE_LIVE_VIDEO = 0x2;
/** /**
* Route type flag for application-specific usage. * Route type flag for application-specific usage.
* *
@@ -219,7 +249,7 @@ public class MediaRouter {
* @hide for use by framework routing UI * @hide for use by framework routing UI
*/ */
public RouteInfo getSystemAudioRoute() { public RouteInfo getSystemAudioRoute() {
return sStatic.mDefaultAudio; return sStatic.mDefaultAudioVideo;
} }
/** /**
@@ -296,7 +326,8 @@ public class MediaRouter {
} }
static void selectRouteStatic(int types, RouteInfo route) { static void selectRouteStatic(int types, RouteInfo route) {
if (sStatic.mSelectedRoute == route) return; final RouteInfo oldRoute = sStatic.mSelectedRoute;
if (oldRoute == route) return;
if ((route.getSupportedTypes() & types) == 0) { if ((route.getSupportedTypes() & types) == 0) {
Log.w(TAG, "selectRoute ignored; cannot select route with supported types " + Log.w(TAG, "selectRoute ignored; cannot select route with supported types " +
typesToString(route.getSupportedTypes()) + " into route types " + typesToString(route.getSupportedTypes()) + " into route types " +
@@ -306,7 +337,7 @@ public class MediaRouter {
final RouteInfo btRoute = sStatic.mBluetoothA2dpRoute; final RouteInfo btRoute = sStatic.mBluetoothA2dpRoute;
if (btRoute != null && (types & ROUTE_TYPE_LIVE_AUDIO) != 0 && if (btRoute != null && (types & ROUTE_TYPE_LIVE_AUDIO) != 0 &&
(route == btRoute || route == sStatic.mDefaultAudio)) { (route == btRoute || route == sStatic.mDefaultAudioVideo)) {
try { try {
sStatic.mAudioService.setBluetoothA2dpOn(route == btRoute); sStatic.mAudioService.setBluetoothA2dpOn(route == btRoute);
} catch (RemoteException e) { } catch (RemoteException e) {
@@ -314,10 +345,21 @@ public class MediaRouter {
} }
} }
if (sStatic.mSelectedRoute != null) { final WifiDisplay activeDisplay =
sStatic.mDisplayService.getWifiDisplayStatus().getActiveDisplay();
final boolean oldRouteHasAddress = oldRoute != null && oldRoute.mDeviceAddress != null;
final boolean newRouteHasAddress = route != null && route.mDeviceAddress != null;
if (activeDisplay != null || oldRouteHasAddress || newRouteHasAddress) {
if (newRouteHasAddress && !matchesDeviceAddress(activeDisplay, route)) {
sStatic.mDisplayService.connectWifiDisplay(route.mDeviceAddress);
} else if (activeDisplay != null && !newRouteHasAddress) {
sStatic.mDisplayService.disconnectWifiDisplay();
}
}
if (oldRoute != null) {
// TODO filter types properly // TODO filter types properly
dispatchRouteUnselected(types & sStatic.mSelectedRoute.getSupportedTypes(), dispatchRouteUnselected(types & oldRoute.getSupportedTypes(), oldRoute);
sStatic.mSelectedRoute);
} }
sStatic.mSelectedRoute = route; sStatic.mSelectedRoute = route;
if (route != null) { if (route != null) {
@@ -326,6 +368,22 @@ public class MediaRouter {
} }
} }
/**
* Compare the device address of a display and a route.
* Nulls/no device address will match another null/no address.
*/
static boolean matchesDeviceAddress(WifiDisplay display, RouteInfo info) {
final boolean routeHasAddress = info != null && info.mDeviceAddress != null;
if (display == null && !routeHasAddress) {
return true;
}
if (display != null && routeHasAddress) {
return display.getDeviceAddress().equals(info.mDeviceAddress);
}
return false;
}
/** /**
* Add an app-specified route for media to the MediaRouter. * Add an app-specified route for media to the MediaRouter.
* App-specified route definitions are created using {@link #createUserRoute(RouteCategory)} * App-specified route definitions are created using {@link #createUserRoute(RouteCategory)}
@@ -419,7 +477,7 @@ public class MediaRouter {
if (info == sStatic.mSelectedRoute) { if (info == sStatic.mSelectedRoute) {
// Removing the currently selected route? Select the default before we remove it. // Removing the currently selected route? Select the default before we remove it.
// TODO: Be smarter about the route types here; this selects for all valid. // TODO: Be smarter about the route types here; this selects for all valid.
selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_USER, sStatic.mDefaultAudio); selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_USER, sStatic.mDefaultAudioVideo);
} }
if (!found) { if (!found) {
sStatic.mCategories.remove(removingCat); sStatic.mCategories.remove(removingCat);
@@ -444,7 +502,8 @@ public class MediaRouter {
if (info == sStatic.mSelectedRoute) { if (info == sStatic.mSelectedRoute) {
// Removing the currently selected route? Select the default before we remove it. // Removing the currently selected route? Select the default before we remove it.
// TODO: Be smarter about the route types here; this selects for all valid. // TODO: Be smarter about the route types here; this selects for all valid.
selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_USER, sStatic.mDefaultAudio); selectRouteStatic(ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO | ROUTE_TYPE_USER,
sStatic.mDefaultAudioVideo);
} }
if (!found) { if (!found) {
sStatic.mCategories.remove(removingCat); sStatic.mCategories.remove(removingCat);
@@ -611,20 +670,151 @@ public class MediaRouter {
if (selectedRoute == null) return; if (selectedRoute == null) return;
if (selectedRoute == sStatic.mBluetoothA2dpRoute || if (selectedRoute == sStatic.mBluetoothA2dpRoute ||
selectedRoute == sStatic.mDefaultAudio) { selectedRoute == sStatic.mDefaultAudioVideo) {
dispatchRouteVolumeChanged(selectedRoute); dispatchRouteVolumeChanged(selectedRoute);
} else if (sStatic.mBluetoothA2dpRoute != null) { } else if (sStatic.mBluetoothA2dpRoute != null) {
try { try {
dispatchRouteVolumeChanged(sStatic.mAudioService.isBluetoothA2dpOn() ? dispatchRouteVolumeChanged(sStatic.mAudioService.isBluetoothA2dpOn() ?
sStatic.mBluetoothA2dpRoute : sStatic.mDefaultAudio); sStatic.mBluetoothA2dpRoute : sStatic.mDefaultAudioVideo);
} catch (RemoteException e) { } catch (RemoteException e) {
Log.e(TAG, "Error checking Bluetooth A2DP state to report volume change", e); Log.e(TAG, "Error checking Bluetooth A2DP state to report volume change", e);
} }
} else { } else {
dispatchRouteVolumeChanged(sStatic.mDefaultAudio); dispatchRouteVolumeChanged(sStatic.mDefaultAudioVideo);
} }
} }
static void updateWifiDisplayStatus(WifiDisplayStatus newStatus) {
final WifiDisplayStatus oldStatus = sStatic.mLastKnownWifiDisplayStatus;
// TODO Naive implementation. Make this smarter later.
boolean needScan = false;
WifiDisplay[] oldDisplays = oldStatus != null ?
oldStatus.getRememberedDisplays() : new WifiDisplay[0];
WifiDisplay[] newDisplays = newStatus.getRememberedDisplays();
WifiDisplay[] availableDisplays = newStatus.getAvailableDisplays();
for (int i = 0; i < newDisplays.length; i++) {
final WifiDisplay d = newDisplays[i];
final WifiDisplay oldRemembered = findMatchingDisplay(d, oldDisplays);
if (oldRemembered == null) {
addRoute(makeWifiDisplayRoute(d));
needScan = true;
} else {
final boolean available = findMatchingDisplay(d, availableDisplays) != null;
final RouteInfo route = findWifiDisplayRoute(d);
updateWifiDisplayRoute(route, d, available, newStatus);
}
}
for (int i = 0; i < oldDisplays.length; i++) {
final WifiDisplay d = oldDisplays[i];
final WifiDisplay newDisplay = findMatchingDisplay(d, newDisplays);
if (newDisplay == null) {
removeRoute(findWifiDisplayRoute(d));
}
}
if (needScan) {
sStatic.mDisplayService.scanWifiDisplays();
}
sStatic.mLastKnownWifiDisplayStatus = newStatus;
}
static RouteInfo makeWifiDisplayRoute(WifiDisplay display) {
final RouteInfo newRoute = new RouteInfo(sStatic.mSystemCategory);
newRoute.mDeviceAddress = display.getDeviceAddress();
newRoute.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO;
newRoute.mVolumeHandling = RouteInfo.PLAYBACK_VOLUME_FIXED;
newRoute.mPlaybackType = RouteInfo.PLAYBACK_TYPE_REMOTE;
newRoute.mStatus = sStatic.mResources.getText(
com.android.internal.R.string.media_route_status_connecting);
newRoute.mEnabled = false;
newRoute.mName = makeWifiDisplayName(display);
return newRoute;
}
static String makeWifiDisplayName(WifiDisplay display) {
String name = display.getDeviceAlias();
if (TextUtils.isEmpty(name)) {
name = display.getDeviceName();
}
return name;
}
private static void updateWifiDisplayRoute(RouteInfo route, WifiDisplay display,
boolean available, WifiDisplayStatus wifiDisplayStatus) {
final boolean isScanning =
wifiDisplayStatus.getScanState() == WifiDisplayStatus.SCAN_STATE_SCANNING;
boolean changed = false;
int newStatus = RouteInfo.STATUS_NONE;
if (available) {
newStatus = isScanning ? RouteInfo.STATUS_SCANNING : RouteInfo.STATUS_AVAILABLE;
} else {
newStatus = RouteInfo.STATUS_NOT_AVAILABLE;
}
if (display.equals(wifiDisplayStatus.getActiveDisplay())) {
final int activeState = wifiDisplayStatus.getActiveDisplayState();
switch (activeState) {
case WifiDisplayStatus.DISPLAY_STATE_CONNECTED:
newStatus = RouteInfo.STATUS_NONE;
break;
case WifiDisplayStatus.DISPLAY_STATE_CONNECTING:
newStatus = RouteInfo.STATUS_CONNECTING;
break;
case WifiDisplayStatus.DISPLAY_STATE_NOT_CONNECTED:
Log.e(TAG, "Active display is not connected!");
break;
}
}
final String newName = makeWifiDisplayName(display);
if (route.getName().equals(newName)) {
route.mName = newName;
changed = true;
}
changed |= route.mEnabled != available;
route.mEnabled = available;
changed |= route.setStatusCode(newStatus);
if (changed) {
dispatchRouteChanged(route);
}
if (!available && route == sStatic.mSelectedRoute) {
// Oops, no longer available. Reselect the default.
final RouteInfo defaultRoute = sStatic.mDefaultAudioVideo;
selectRouteStatic(defaultRoute.getSupportedTypes(), defaultRoute);
}
}
private static WifiDisplay findMatchingDisplay(WifiDisplay address, WifiDisplay[] displays) {
for (int i = 0; i < displays.length; i++) {
final WifiDisplay d = displays[i];
if (d.equals(address)) {
return d;
}
}
return null;
}
private static RouteInfo findWifiDisplayRoute(WifiDisplay d) {
final int count = sStatic.mRoutes.size();
for (int i = 0; i < count; i++) {
final RouteInfo info = sStatic.mRoutes.get(i);
if (d.getDeviceAddress().equals(info.mDeviceAddress)) {
return info;
}
}
return null;
}
/** /**
* Information about a media route. * Information about a media route.
*/ */
@@ -644,6 +834,18 @@ public class MediaRouter {
int mPlaybackStream = AudioManager.STREAM_MUSIC; int mPlaybackStream = AudioManager.STREAM_MUSIC;
VolumeCallbackInfo mVcb; VolumeCallbackInfo mVcb;
String mDeviceAddress;
boolean mEnabled = true;
// A predetermined connection status that can override mStatus
private int mStatusCode;
static final int STATUS_NONE = 0;
static final int STATUS_SCANNING = 1;
static final int STATUS_CONNECTING = 2;
static final int STATUS_AVAILABLE = 3;
static final int STATUS_NOT_AVAILABLE = 4;
private Object mTag; private Object mTag;
/** /**
@@ -710,6 +912,34 @@ public class MediaRouter {
return mStatus; return mStatus;
} }
/**
* Set this route's status by predetermined status code. If the caller
* should dispatch a route changed event this call will return true;
*/
boolean setStatusCode(int statusCode) {
if (statusCode != mStatusCode) {
mStatusCode = statusCode;
int resId = 0;
switch (statusCode) {
case STATUS_SCANNING:
resId = com.android.internal.R.string.media_route_status_scanning;
break;
case STATUS_CONNECTING:
resId = com.android.internal.R.string.media_route_status_connecting;
break;
case STATUS_AVAILABLE:
resId = com.android.internal.R.string.media_route_status_available;
break;
case STATUS_NOT_AVAILABLE:
resId = com.android.internal.R.string.media_route_status_not_available;
break;
}
mStatus = resId != 0 ? sStatic.mResources.getText(resId) : null;
return true;
}
return false;
}
/** /**
* @return A media type flag set describing which types this route supports. * @return A media type flag set describing which types this route supports.
*/ */
@@ -866,6 +1096,13 @@ public class MediaRouter {
return mVolumeHandling; return mVolumeHandling;
} }
/**
* @return true if this route is enabled and may be selected
*/
public boolean isEnabled() {
return mEnabled;
}
void setStatusInt(CharSequence status) { void setStatusInt(CharSequence status) {
if (!status.equals(mStatus)) { if (!status.equals(mStatus)) {
mStatus = status; mStatus = status;
@@ -881,7 +1118,6 @@ public class MediaRouter {
sStatic.mHandler.post(new Runnable() { sStatic.mHandler.post(new Runnable() {
@Override @Override
public void run() { public void run() {
//Log.d(TAG, "dispatchRemoteVolumeUpdate dir=" + direction + " val=" + value);
if (mVcb != null) { if (mVcb != null) {
if (direction != 0) { if (direction != 0) {
mVcb.vcb.onVolumeUpdateRequest(mVcb.route, direction); mVcb.vcb.onVolumeUpdateRequest(mVcb.route, direction);
@@ -1400,6 +1636,7 @@ public class MediaRouter {
int mNameResId; int mNameResId;
int mTypes; int mTypes;
final boolean mGroupable; final boolean mGroupable;
boolean mIsSystem;
RouteCategory(CharSequence name, int types, boolean groupable) { RouteCategory(CharSequence name, int types, boolean groupable) {
mName = name; mName = name;
@@ -1486,6 +1723,14 @@ public class MediaRouter {
return mGroupable; return mGroupable;
} }
/**
* @return true if this is the category reserved for system routes.
* @hide
*/
public boolean isSystem() {
return mIsSystem;
}
public String toString() { public String toString() {
return "RouteCategory{ name=" + mName + " types=" + typesToString(mTypes) + return "RouteCategory{ name=" + mName + " types=" + typesToString(mTypes) +
" groupable=" + mGroupable + " }"; " groupable=" + mGroupable + " }";
@@ -1671,7 +1916,6 @@ public class MediaRouter {
} }
static class VolumeChangeReceiver extends BroadcastReceiver { static class VolumeChangeReceiver extends BroadcastReceiver {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(AudioManager.VOLUME_CHANGED_ACTION)) { if (intent.getAction().equals(AudioManager.VOLUME_CHANGED_ACTION)) {
@@ -1689,6 +1933,15 @@ public class MediaRouter {
} }
} }
} }
}
static class WifiDisplayStatusChangedReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED)) {
updateWifiDisplayStatus((WifiDisplayStatus) intent.getParcelableExtra(
DisplayManager.EXTRA_WIFI_DISPLAY_STATUS));
}
}
} }
} }