Handles volume for device route and bt routes.
AudioManager#getStreamVolume() is used to get volume info.
Which returns device volume or bluetooth volume according to the
current active device.
The selected route of the system media route provider can be different
from the one in AudioManager, which results in having wrong volume in
MediaRoute2Info.
This CL updates the volume of the correct media route depending on
AudioManager#getDevicesForStream.
It assumes that there are 3 volumes (device volume, bluetooth volume,
and hearing aid volume).
The limitation of this CL is that when the user transfers to a route
first time, the volume could be 0. About after 4~600ms volume info is
updated.
This seems inevitable because I couldn't find an API to get the volume
of inactive device.
Bug: 155279794
Test: manually switching between phone <-> bt route. Reboot multiple
times to check "first transfer"
Change-Id: I4edcab9e6e436801b64c9434b683ddddbbc02bef
diff --git a/services/core/java/com/android/server/media/BluetoothRouteProvider.java b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
index 54958d3..fd8e03e 100644
--- a/services/core/java/com/android/server/media/BluetoothRouteProvider.java
+++ b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
@@ -28,10 +28,12 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
+import android.media.AudioSystem;
import android.media.MediaRoute2Info;
import android.text.TextUtils;
import android.util.Slog;
import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
import com.android.internal.R;
@@ -55,6 +57,9 @@
@SuppressWarnings("WeakerAccess") /* synthetic access */
BluetoothHearingAid mHearingAidProfile;
+ // Route type -> volume map
+ private final SparseIntArray mVolumeMap = new SparseIntArray();
+
private final Context mContext;
private final BluetoothAdapter mBluetoothAdapter;
private final BluetoothRoutesUpdatedListener mListener;
@@ -192,11 +197,30 @@
return routes;
}
- boolean setSelectedRouteVolume(int volume) {
- if (mSelectedRoute == null) return false;
+ /**
+ * Updates the volume for {@link AudioManager#getDevicesForStream(int) devices}.
+ *
+ * @return true if devices can be handled by the provider.
+ */
+ public boolean updateVolumeForDevices(int devices, int volume) {
+ int routeType;
+ if ((devices & (AudioSystem.DEVICE_OUT_HEARING_AID)) != 0) {
+ routeType = MediaRoute2Info.TYPE_HEARING_AID;
+ } else if ((devices & (AudioManager.DEVICE_OUT_BLUETOOTH_A2DP
+ | AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES
+ | AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER)) != 0) {
+ routeType = MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
+ } else {
+ return false;
+ }
+ mVolumeMap.put(routeType, volume);
+ if (mSelectedRoute == null || mSelectedRoute.route.getType() != routeType) {
+ return true;
+ }
mSelectedRoute.route = new MediaRoute2Info.Builder(mSelectedRoute.route)
.setVolume(volume)
.build();
+ notifyBluetoothRoutesUpdated();
return true;
}
@@ -222,6 +246,7 @@
R.string.bluetooth_a2dp_audio_route_name).toString())
.setType(MediaRoute2Info.TYPE_BLUETOOTH_A2DP)
.setVolumeHandling(MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
+ .setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
.build();
newBtRoute.connectedProfiles = new SparseBooleanArray();
return newBtRoute;
@@ -240,13 +265,10 @@
// Update volume when the connection state is changed.
MediaRoute2Info.Builder builder = new MediaRoute2Info.Builder(btRoute.route)
.setConnectionState(state);
- builder.setType(btRoute.connectedProfiles.get(BluetoothProfile.HEARING_AID, false)
- ? MediaRoute2Info.TYPE_HEARING_AID : MediaRoute2Info.TYPE_BLUETOOTH_A2DP);
+ builder.setType(btRoute.getRouteType());
if (state == MediaRoute2Info.CONNECTION_STATE_CONNECTED) {
- int maxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
- int currentVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
- builder.setVolumeMax(maxVolume).setVolume(currentVolume);
+ builder.setVolume(mVolumeMap.get(btRoute.getRouteType(), 0));
}
btRoute.route = builder.build();
}
@@ -259,6 +281,15 @@
public BluetoothDevice btDevice;
public MediaRoute2Info route;
public SparseBooleanArray connectedProfiles;
+
+ @MediaRoute2Info.Type
+ int getRouteType() {
+ // Let hearing aid profile have a priority.
+ if (connectedProfiles.get(BluetoothProfile.HEARING_AID, false)) {
+ return MediaRoute2Info.TYPE_HEARING_AID;
+ }
+ return MediaRoute2Info.TYPE_BLUETOOTH_A2DP;
+ }
}
// These callbacks run on the main thread.
@@ -285,13 +316,12 @@
btRoute = createBluetoothRoute(device);
mBluetoothRoutes.put(device.getAddress(), btRoute);
}
+ btRoute.connectedProfiles.put(profile, true);
if (activeDevices.contains(device)) {
mSelectedRoute = btRoute;
setRouteConnectionState(mSelectedRoute,
MediaRoute2Info.CONNECTION_STATE_CONNECTED);
}
-
- btRoute.connectedProfiles.put(profile, true);
}
notifyBluetoothRoutesUpdated();
}
@@ -348,6 +378,8 @@
BluetoothDevice.ERROR);
BluetoothRouteInfo btRoute = mBluetoothRoutes.get(device.getAddress());
if (bondState == BluetoothDevice.BOND_BONDED && btRoute == null) {
+ //TODO: The type of the new route is A2DP even when it's HEARING_AID.
+ // We may determine the type of route when create the route.
btRoute = createBluetoothRoute(device);
if (mA2dpProfile != null && mA2dpProfile.getConnectedDevices().contains(device)) {
btRoute.connectedProfiles.put(BluetoothProfile.A2DP, true);
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index b585b49..fdee9f8 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -81,6 +81,7 @@
MediaRoute2Info mDeviceRoute;
RoutingSessionInfo mDefaultSessionInfo;
final AudioRoutesInfo mCurAudioRoutesInfo = new AudioRoutesInfo();
+ int mDeviceVolume;
private final Object mRequestLock = new Object();
@GuardedBy("mRequestLock")
@@ -127,8 +128,9 @@
});
updateSessionInfosIfNeeded();
- mContext.registerReceiver(new VolumeChangeReceiver(),
- new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION));
+ IntentFilter intentFilter = new IntentFilter(AudioManager.VOLUME_CHANGED_ACTION);
+ intentFilter.addAction(AudioManager.STREAM_DEVICES_CHANGED_ACTION);
+ mContext.registerReceiver(new AudioManagerBroadcastReceiver(), intentFilter);
if (mBtRouteProvider != null) {
mHandler.post(() -> {
@@ -136,6 +138,7 @@
notifyProviderState();
});
}
+ updateVolume();
}
@Override
@@ -248,8 +251,8 @@
.setVolumeHandling(mAudioManager.isVolumeFixed()
? MediaRoute2Info.PLAYBACK_VOLUME_FIXED
: MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
+ .setVolume(mDeviceVolume)
.setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
- .setVolume(mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC))
.setType(type)
.addFeature(FEATURE_LIVE_AUDIO)
.addFeature(FEATURE_LIVE_VIDEO)
@@ -361,36 +364,43 @@
}
}
- private class VolumeChangeReceiver extends BroadcastReceiver {
+ void updateVolume() {
+ int devices = mAudioManager.getDevicesForStream(AudioManager.STREAM_MUSIC);
+ int volume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+
+ if (mDefaultRoute.getVolume() != volume) {
+ mDefaultRoute = new MediaRoute2Info.Builder(mDefaultRoute)
+ .setVolume(volume)
+ .build();
+ }
+
+ if (mBtRouteProvider != null && mBtRouteProvider.updateVolumeForDevices(devices, volume)) {
+ return;
+ }
+ if (mDeviceVolume != volume) {
+ mDeviceVolume = volume;
+ mDeviceRoute = new MediaRoute2Info.Builder(mDeviceRoute)
+ .setVolume(volume)
+ .build();
+ }
+ publishProviderState();
+ }
+
+ private class AudioManagerBroadcastReceiver extends BroadcastReceiver {
// This will be called in the main thread.
@Override
public void onReceive(Context context, Intent intent) {
- if (!intent.getAction().equals(AudioManager.VOLUME_CHANGED_ACTION)) {
+ if (!intent.getAction().equals(AudioManager.VOLUME_CHANGED_ACTION)
+ && !intent.getAction().equals(AudioManager.STREAM_DEVICES_CHANGED_ACTION)) {
return;
}
- final int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
+ 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) {
- if (TextUtils.equals(mDeviceRoute.getId(), mSelectedRouteId)) {
- mDeviceRoute = new MediaRoute2Info.Builder(mDeviceRoute)
- .setVolume(newVolume)
- .build();
- } else if (mBtRouteProvider != null) {
- mBtRouteProvider.setSelectedRouteVolume(newVolume);
- }
- mDefaultRoute = new MediaRoute2Info.Builder(mDefaultRoute)
- .setVolume(newVolume)
- .build();
- publishProviderState();
- }
+ updateVolume();
}
}
}