From f8f170cb2120f1c101be062b8601a2a76e20505e Mon Sep 17 00:00:00 2001 From: Kyunglyul Hyun Date: Wed, 13 May 2020 21:31:26 +0900 Subject: [PATCH] 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 --- .../server/media/BluetoothRouteProvider.java | 50 ++++++++++++++--- .../media/SystemMediaRoute2Provider.java | 56 +++++++++++-------- 2 files changed, 74 insertions(+), 32 deletions(-) diff --git a/services/core/java/com/android/server/media/BluetoothRouteProvider.java b/services/core/java/com/android/server/media/BluetoothRouteProvider.java index 54958d3470965..fd8e03e759f8c 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.Context; 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 @@ class BluetoothRouteProvider { @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 @@ class BluetoothRouteProvider { 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 @@ class BluetoothRouteProvider { 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 @@ class BluetoothRouteProvider { // 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 @@ class BluetoothRouteProvider { 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 @@ class BluetoothRouteProvider { 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 @@ class BluetoothRouteProvider { 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 b585b49928cd4..fdee9f86bfafa 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 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { MediaRoute2Info mDeviceRoute; RoutingSessionInfo mDefaultSessionInfo; final AudioRoutesInfo mCurAudioRoutesInfo = new AudioRoutesInfo(); + int mDeviceVolume; private final Object mRequestLock = new Object(); @GuardedBy("mRequestLock") @@ -127,8 +128,9 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { }); 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 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { notifyProviderState(); }); } + updateVolume(); } @Override @@ -248,8 +251,8 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { .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 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { } } - 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(); } } }