Expand volume APIs for MediaRouter
Allow applications to set a requested volume level on RouteInfo
objects. If requested for a user route, the app-supplied callback will
be invoked to perform actual volume adjustment.
Change-Id: I856990a0da7292492aa15e6562dbc3d055b848a0
diff --git a/api/current.txt b/api/current.txt
index 3069221..f25b11e 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -11538,6 +11538,7 @@
method public abstract void onRouteSelected(android.media.MediaRouter, int, android.media.MediaRouter.RouteInfo);
method public abstract void onRouteUngrouped(android.media.MediaRouter, android.media.MediaRouter.RouteInfo, android.media.MediaRouter.RouteGroup);
method public abstract void onRouteUnselected(android.media.MediaRouter, int, android.media.MediaRouter.RouteInfo);
+ method public abstract void onRouteVolumeChanged(android.media.MediaRouter, android.media.MediaRouter.RouteInfo);
}
public static class MediaRouter.RouteCategory {
@@ -11573,6 +11574,8 @@
method public int getVolume();
method public int getVolumeHandling();
method public int getVolumeMax();
+ method public void requestSetVolume(int);
+ method public void requestUpdateVolume(int);
method public void setTag(java.lang.Object);
field public static final int PLAYBACK_TYPE_LOCAL = 0; // 0x0
field public static final int PLAYBACK_TYPE_REMOTE = 1; // 0x1
@@ -11589,6 +11592,7 @@
method public void onRouteSelected(android.media.MediaRouter, int, android.media.MediaRouter.RouteInfo);
method public void onRouteUngrouped(android.media.MediaRouter, android.media.MediaRouter.RouteInfo, android.media.MediaRouter.RouteGroup);
method public void onRouteUnselected(android.media.MediaRouter, int, android.media.MediaRouter.RouteInfo);
+ method public void onRouteVolumeChanged(android.media.MediaRouter, android.media.MediaRouter.RouteInfo);
}
public static class MediaRouter.UserRouteInfo extends android.media.MediaRouter.RouteInfo {
diff --git a/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java b/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java
index b615b9c..b747694 100644
--- a/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java
+++ b/core/java/com/android/internal/app/MediaRouteChooserDialogFragment.java
@@ -30,8 +30,6 @@
import android.media.MediaRouter.RouteCategory;
import android.media.MediaRouter.RouteGroup;
import android.media.MediaRouter.RouteInfo;
-import android.media.MediaRouter.UserRouteInfo;
-import android.media.RemoteControlClient;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.KeyEvent;
@@ -85,7 +83,8 @@
final RouteComparator mComparator = new RouteComparator();
final MediaRouterCallback mCallback = new MediaRouterCallback();
- private boolean mIgnoreVolumeChanges;
+ private boolean mIgnoreSliderVolumeChanges;
+ private boolean mIgnoreCallbackVolumeChanges;
public MediaRouteChooserDialogFragment() {
setStyle(STYLE_NO_TITLE, R.style.Theme_DeviceDefault_Dialog);
@@ -126,52 +125,34 @@
void updateVolume() {
final RouteInfo selectedRoute = mRouter.getSelectedRoute(mRouteTypes);
- final boolean defaultAudioSelected = selectedRoute == mRouter.getSystemAudioRoute();
- final boolean selectedSystemRoute =
- selectedRoute.getCategory() == mRouter.getSystemAudioCategory();
- mVolumeIcon.setImageResource(defaultAudioSelected ?
+ mVolumeIcon.setImageResource(
+ selectedRoute.getPlaybackType() == RouteInfo.PLAYBACK_TYPE_LOCAL ?
R.drawable.ic_audio_vol : R.drawable.ic_media_route_on_holo_dark);
- mIgnoreVolumeChanges = true;
- mVolumeSlider.setEnabled(true);
- if (selectedSystemRoute) {
- // Use the standard media audio stream
- mVolumeSlider.setMax(mAudio.getStreamMaxVolume(AudioManager.STREAM_MUSIC));
- mVolumeSlider.setProgress(mAudio.getStreamVolume(AudioManager.STREAM_MUSIC));
+ mIgnoreSliderVolumeChanges = true;
+
+ if (selectedRoute.getVolumeHandling() == RouteInfo.PLAYBACK_VOLUME_FIXED) {
+ // Disable the slider and show it at max volume.
+ mVolumeSlider.setMax(1);
+ mVolumeSlider.setProgress(1);
+ mVolumeSlider.setEnabled(false);
} else {
- final RouteInfo firstSelected;
- if (selectedRoute instanceof RouteGroup) {
- firstSelected = ((RouteGroup) selectedRoute).getRouteAt(0);
- } else {
- firstSelected = selectedRoute;
- }
-
- RemoteControlClient rcc = null;
- if (firstSelected instanceof UserRouteInfo) {
- rcc = ((UserRouteInfo) firstSelected).getRemoteControlClient();
- }
-
- if (rcc == null) {
- // No RemoteControlClient? Assume volume can't be controlled.
- // Disable the slider and show it at max volume.
- mVolumeSlider.setMax(1);
- mVolumeSlider.setProgress(1);
- mVolumeSlider.setEnabled(false);
- } else {
- // TODO: Connect this to the remote control volume
- }
+ mVolumeSlider.setEnabled(true);
+ mVolumeSlider.setMax(selectedRoute.getVolumeMax());
+ mVolumeSlider.setProgress(selectedRoute.getVolume());
}
- mIgnoreVolumeChanges = false;
+
+ mIgnoreSliderVolumeChanges = false;
}
void changeVolume(int newValue) {
- if (mIgnoreVolumeChanges) return;
+ if (mIgnoreSliderVolumeChanges) return;
- RouteCategory selectedCategory = mRouter.getSelectedRoute(mRouteTypes).getCategory();
- if (selectedCategory == mRouter.getSystemAudioCategory()) {
+ final RouteInfo selectedRoute = mRouter.getSelectedRoute(mRouteTypes);
+ if (selectedRoute.getVolumeHandling() == RouteInfo.PLAYBACK_VOLUME_VARIABLE) {
final int maxVolume = mAudio.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
newValue = Math.max(0, Math.min(newValue, maxVolume));
- mAudio.setStreamVolume(AudioManager.STREAM_MUSIC, newValue, 0);
+ selectedRoute.requestSetVolume(newValue);
}
}
@@ -595,7 +576,6 @@
@Override
public void onRouteAdded(MediaRouter router, RouteInfo info) {
mAdapter.update();
- updateVolume();
}
@Override
@@ -604,7 +584,6 @@
mAdapter.finishGrouping();
}
mAdapter.update();
- updateVolume();
}
@Override
@@ -622,6 +601,13 @@
public void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group) {
mAdapter.update();
}
+
+ @Override
+ public void onRouteVolumeChanged(MediaRouter router, RouteInfo info) {
+ if (!mIgnoreCallbackVolumeChanges) {
+ updateVolume();
+ }
+ }
}
class RouteComparator implements Comparator<RouteInfo> {
@@ -648,15 +634,25 @@
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN && mVolumeSlider.isEnabled()) {
- mVolumeSlider.incrementProgressBy(-1);
+ mRouter.getSelectedRoute(mRouteTypes).requestUpdateVolume(-1);
return true;
} else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP && mVolumeSlider.isEnabled()) {
- mVolumeSlider.incrementProgressBy(1);
+ mRouter.getSelectedRoute(mRouteTypes).requestUpdateVolume(1);
return true;
} else {
return super.onKeyDown(keyCode, event);
}
}
+
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN && mVolumeSlider.isEnabled()) {
+ return true;
+ } else if (keyCode == KeyEvent.KEYCODE_VOLUME_UP && mVolumeSlider.isEnabled()) {
+ return true;
+ } else {
+ return super.onKeyUp(keyCode, event);
+ }
+ }
}
/**
@@ -675,10 +671,13 @@
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
+ mIgnoreCallbackVolumeChanges = true;
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
+ mIgnoreCallbackVolumeChanges = false;
+ updateVolume();
}
}
diff --git a/media/java/android/media/MediaRouter.java b/media/java/android/media/MediaRouter.java
index b6187da..3de2db2 100644
--- a/media/java/android/media/MediaRouter.java
+++ b/media/java/android/media/MediaRouter.java
@@ -16,7 +16,10 @@
package android.media;
+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;
@@ -87,12 +90,15 @@
}
// Called after sStatic is initialized
- void startMonitoringRoutes() {
+ void startMonitoringRoutes(Context appContext) {
mDefaultAudio = new RouteInfo(mSystemCategory);
mDefaultAudio.mNameResId = com.android.internal.R.string.default_audio_route_name;
mDefaultAudio.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO;
addRoute(mDefaultAudio);
+ appContext.registerReceiver(new VolumeChangeReceiver(),
+ new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION));
+
AudioRoutesInfo newRoutes = null;
try {
newRoutes = mAudioService.startWatchingRoutes(mRoutesObserver);
@@ -190,8 +196,9 @@
public MediaRouter(Context context) {
synchronized (Static.class) {
if (sStatic == null) {
- sStatic = new Static(context.getApplicationContext());
- sStatic.startMonitoringRoutes();
+ final Context appContext = context.getApplicationContext();
+ sStatic = new Static(appContext);
+ sStatic.startMonitoringRoutes(appContext);
}
}
}
@@ -578,6 +585,33 @@
}
}
+ static void dispatchRouteVolumeChanged(RouteInfo info) {
+ for (CallbackInfo cbi : sStatic.mCallbacks) {
+ if ((cbi.type & info.mSupportedTypes) != 0) {
+ cbi.cb.onRouteVolumeChanged(cbi.router, info);
+ }
+ }
+ }
+
+ static void systemVolumeChanged(int newValue) {
+ final RouteInfo selectedRoute = sStatic.mSelectedRoute;
+ if (selectedRoute == null) return;
+
+ if (selectedRoute == sStatic.mBluetoothA2dpRoute ||
+ selectedRoute == sStatic.mDefaultAudio) {
+ dispatchRouteVolumeChanged(selectedRoute);
+ } else if (sStatic.mBluetoothA2dpRoute != null) {
+ try {
+ dispatchRouteVolumeChanged(sStatic.mAudioService.isBluetoothA2dpOn() ?
+ sStatic.mBluetoothA2dpRoute : sStatic.mDefaultAudio);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error checking Bluetooth A2DP state to report volume change", e);
+ }
+ } else {
+ dispatchRouteVolumeChanged(sStatic.mDefaultAudio);
+ }
+ }
+
/**
* Information about a media route.
*/
@@ -735,6 +769,9 @@
}
/**
+ * Return the current volume for this route. Depending on the route, this may only
+ * be valid if the route is currently selected.
+ *
* @return the volume at which the playback associated with this route is performed
* @see UserRouteInfo#setVolume(int)
*/
@@ -753,6 +790,44 @@
}
/**
+ * Request a volume change for this route.
+ * @param volume value between 0 and getVolumeMax
+ */
+ public void requestSetVolume(int volume) {
+ if (mPlaybackType == PLAYBACK_TYPE_LOCAL) {
+ try {
+ sStatic.mAudioService.setStreamVolume(mPlaybackStream, volume, 0);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error setting local stream volume", e);
+ }
+ } else {
+ Log.e(TAG, getClass().getSimpleName() + ".requestSetVolume(): " +
+ "Non-local volume playback on system route? " +
+ "Could not request volume change.");
+ }
+ }
+
+ /**
+ * Request an incremental volume update for this route.
+ * @param direction Delta to apply to the current volume
+ */
+ public void requestUpdateVolume(int direction) {
+ if (mPlaybackType == PLAYBACK_TYPE_LOCAL) {
+ try {
+ final int volume =
+ Math.max(0, Math.min(getVolume() + direction, getVolumeMax()));
+ sStatic.mAudioService.setStreamVolume(mPlaybackStream, volume, 0);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error setting local stream volume", e);
+ }
+ } else {
+ Log.e(TAG, getClass().getSimpleName() + ".requestChangeVolume(): " +
+ "Non-local volume playback on system route? " +
+ "Could not request volume change.");
+ }
+ }
+
+ /**
* @return the maximum volume at which the playback associated with this route is performed
* @see UserRouteInfo#setVolumeMax(int)
*/
@@ -821,6 +896,8 @@
/**
* Information about a route that the application may define and modify.
+ * A user route defaults to {@link RouteInfo#PLAYBACK_TYPE_REMOTE} and
+ * {@link RouteInfo#PLAYBACK_VOLUME_FIXED}.
*
* @see MediaRouter.RouteInfo
*/
@@ -830,6 +907,8 @@
UserRouteInfo(RouteCategory category) {
super(category);
mSupportedTypes = ROUTE_TYPE_USER;
+ mPlaybackType = PLAYBACK_TYPE_REMOTE;
+ mVolumeHandling = PLAYBACK_VOLUME_FIXED;
}
/**
@@ -949,9 +1028,33 @@
* @param volume
*/
public void setVolume(int volume) {
+ volume = Math.max(0, Math.min(volume, getVolumeMax()));
if (mVolume != volume) {
mVolume = volume;
setPlaybackInfoOnRcc(RemoteControlClient.PLAYBACKINFO_VOLUME, volume);
+ dispatchRouteVolumeChanged(this);
+ }
+ }
+
+ @Override
+ public void requestSetVolume(int volume) {
+ if (mVolumeHandling == PLAYBACK_VOLUME_VARIABLE) {
+ if (mVcb == null) {
+ Log.e(TAG, "Cannot requestSetVolume on user route - no volume callback set");
+ return;
+ }
+ mVcb.vcb.onVolumeSetRequest(this, volume);
+ }
+ }
+
+ @Override
+ public void requestUpdateVolume(int direction) {
+ if (mVolumeHandling == PLAYBACK_VOLUME_VARIABLE) {
+ if (mVcb == null) {
+ Log.e(TAG, "Cannot requestChangeVolume on user route - no volumec callback set");
+ return;
+ }
+ mVcb.vcb.onVolumeUpdateRequest(this, direction);
}
}
@@ -1018,6 +1121,7 @@
RouteGroup(RouteCategory category) {
super(category);
mGroup = this;
+ mVolumeHandling = PLAYBACK_VOLUME_FIXED;
}
CharSequence getName(Resources res) {
@@ -1138,6 +1242,45 @@
setIconDrawable(sStatic.mResources.getDrawable(resId));
}
+ @Override
+ public void requestSetVolume(int volume) {
+ final int maxVol = getVolumeMax();
+ if (maxVol == 0) {
+ return;
+ }
+
+ final float scaledVolume = (float) volume / maxVol;
+ final int routeCount = getRouteCount();
+ for (int i = 0; i < routeCount; i++) {
+ final RouteInfo route = getRouteAt(i);
+ final int routeVol = (int) (scaledVolume * route.getVolumeMax());
+ route.requestSetVolume(routeVol);
+ }
+ if (volume != mVolume) {
+ mVolume = volume;
+ dispatchRouteVolumeChanged(this);
+ }
+ }
+
+ @Override
+ public void requestUpdateVolume(int direction) {
+ final int maxVol = getVolumeMax();
+ if (maxVol == 0) {
+ return;
+ }
+
+ final int routeCount = getRouteCount();
+ for (int i = 0; i < routeCount; i++) {
+ final RouteInfo route = getRouteAt(i);
+ route.requestUpdateVolume(direction);
+ }
+ final int volume = Math.max(0, Math.min(mVolume + direction, maxVol));
+ if (volume != mVolume) {
+ mVolume = volume;
+ dispatchRouteVolumeChanged(this);
+ }
+ }
+
void memberNameChanged(RouteInfo info, CharSequence name) {
mUpdateName = true;
routeUpdated();
@@ -1157,10 +1300,23 @@
return;
}
+ int maxVolume = 0;
+ boolean isLocal = true;
+ boolean isFixedVolume = true;
for (int i = 0; i < count; i++) {
- types |= mRoutes.get(i).mSupportedTypes;
+ final RouteInfo route = mRoutes.get(i);
+ types |= route.mSupportedTypes;
+ final int routeMaxVolume = route.getVolumeMax();
+ if (routeMaxVolume > maxVolume) {
+ maxVolume = routeMaxVolume;
+ }
+ isLocal &= route.getPlaybackType() == PLAYBACK_TYPE_LOCAL;
+ isFixedVolume &= route.getVolumeHandling() == PLAYBACK_VOLUME_FIXED;
}
+ mPlaybackType = isLocal ? PLAYBACK_TYPE_LOCAL : PLAYBACK_TYPE_REMOTE;
+ mVolumeHandling = isFixedVolume ? PLAYBACK_VOLUME_FIXED : PLAYBACK_VOLUME_VARIABLE;
mSupportedTypes = types;
+ mVolumeMax = maxVolume;
mIcon = count == 1 ? mRoutes.get(0).getIconDrawable() : null;
super.routeUpdated();
}
@@ -1381,6 +1537,14 @@
* @param group The group the route was removed from
*/
public abstract void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group);
+
+ /**
+ * Called when a route's volume changes.
+ *
+ * @param router the MediaRouter reporting the event
+ * @param info The route with altered volume
+ */
+ public abstract void onRouteVolumeChanged(MediaRouter router, RouteInfo info);
}
/**
@@ -1419,6 +1583,9 @@
public void onRouteUngrouped(MediaRouter router, RouteInfo info, RouteGroup group) {
}
+ @Override
+ public void onRouteVolumeChanged(MediaRouter router, RouteInfo info) {
+ }
}
static class VolumeCallbackInfo {
@@ -1459,4 +1626,25 @@
public abstract void onVolumeSetRequest(RouteInfo info, int volume);
}
+ static class VolumeChangeReceiver extends BroadcastReceiver {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(AudioManager.VOLUME_CHANGED_ACTION)) {
+ final int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE,
+ -1);
+ if (streamType != AudioManager.STREAM_MUSIC) {
+ return;
+ }
+
+ final int newVolume = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0);
+ final int oldVolume = intent.getIntExtra(
+ AudioManager.EXTRA_PREV_VOLUME_STREAM_VALUE, 0);
+ if (newVolume != oldVolume) {
+ systemVolumeChanged(newVolume);
+ }
+ }
+ }
+
+ }
}