From 6c1195c0248835d7c293701fa7762a1337d87251 Mon Sep 17 00:00:00 2001 From: Alex Dadukin Date: Wed, 1 Mar 2023 17:25:16 +0000 Subject: [PATCH] Subscribe to AudioManager to listen to currently selected device Bug: b/255495104 Test: manually tested Change-Id: I360b1ef592e30f4ad39136ec0def2f3fa74df912 --- .../server/media/AudioAttributesUtils.java | 112 ++++++++++++++++++ .../media/SystemMediaRoute2Provider.java | 51 ++++++++ 2 files changed, 163 insertions(+) create mode 100644 services/core/java/com/android/server/media/AudioAttributesUtils.java diff --git a/services/core/java/com/android/server/media/AudioAttributesUtils.java b/services/core/java/com/android/server/media/AudioAttributesUtils.java new file mode 100644 index 0000000000000..b9c9bae8e62b9 --- /dev/null +++ b/services/core/java/com/android/server/media/AudioAttributesUtils.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2023 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 com.android.server.media; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.media.AudioAttributes; +import android.media.AudioDeviceAttributes; +import android.media.AudioDeviceInfo; +import android.media.MediaRoute2Info; + +/* package */ final class AudioAttributesUtils { + + /* package */ static final AudioAttributes ATTRIBUTES_MEDIA = new AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_MEDIA) + .build(); + + private AudioAttributesUtils() { + // no-op to prevent instantiation. + } + + @MediaRoute2Info.Type + /* package */ static int mapToMediaRouteType( + @NonNull AudioDeviceAttributes audioDeviceAttributes) { + switch (audioDeviceAttributes.getType()) { + case AudioDeviceInfo.TYPE_BUILTIN_EARPIECE: + case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER: + return MediaRoute2Info.TYPE_BUILTIN_SPEAKER; + case AudioDeviceInfo.TYPE_WIRED_HEADSET: + return MediaRoute2Info.TYPE_WIRED_HEADSET; + case AudioDeviceInfo.TYPE_WIRED_HEADPHONES: + return MediaRoute2Info.TYPE_WIRED_HEADPHONES; + case AudioDeviceInfo.TYPE_DOCK: + case AudioDeviceInfo.TYPE_DOCK_ANALOG: + return MediaRoute2Info.TYPE_DOCK; + case AudioDeviceInfo.TYPE_HDMI: + return MediaRoute2Info.TYPE_HDMI; + case AudioDeviceInfo.TYPE_USB_DEVICE: + return MediaRoute2Info.TYPE_USB_DEVICE; + case AudioDeviceInfo.TYPE_BLUETOOTH_A2DP: + return MediaRoute2Info.TYPE_BLUETOOTH_A2DP; + case AudioDeviceInfo.TYPE_BLE_HEADSET: + return MediaRoute2Info.TYPE_BLE_HEADSET; + case AudioDeviceInfo.TYPE_HEARING_AID: + return MediaRoute2Info.TYPE_HEARING_AID; + default: + return MediaRoute2Info.TYPE_UNKNOWN; + } + } + + + /* package */ static boolean isDeviceOutputAttributes( + @Nullable AudioDeviceAttributes audioDeviceAttributes) { + if (audioDeviceAttributes == null) { + return false; + } + + if (audioDeviceAttributes.getRole() != AudioDeviceAttributes.ROLE_OUTPUT) { + return false; + } + + switch (audioDeviceAttributes.getType()) { + case AudioDeviceInfo.TYPE_BUILTIN_EARPIECE: + case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER: + case AudioDeviceInfo.TYPE_WIRED_HEADSET: + case AudioDeviceInfo.TYPE_WIRED_HEADPHONES: + case AudioDeviceInfo.TYPE_DOCK: + case AudioDeviceInfo.TYPE_DOCK_ANALOG: + case AudioDeviceInfo.TYPE_HDMI: + case AudioDeviceInfo.TYPE_USB_DEVICE: + return true; + default: + return false; + } + } + + /* package */ static boolean isBluetoothOutputAttributes( + @Nullable AudioDeviceAttributes audioDeviceAttributes) { + if (audioDeviceAttributes == null) { + return false; + } + + if (audioDeviceAttributes.getRole() != AudioDeviceAttributes.ROLE_OUTPUT) { + return false; + } + + switch (audioDeviceAttributes.getType()) { + case AudioDeviceInfo.TYPE_BLUETOOTH_A2DP: + case AudioDeviceInfo.TYPE_BLE_HEADSET: + case AudioDeviceInfo.TYPE_BLE_SPEAKER: + case AudioDeviceInfo.TYPE_HEARING_AID: + return true; + default: + return false; + } + } + +} diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java index 6619e6c3d26fc..5d5c621eb3f58 100644 --- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java @@ -16,11 +16,14 @@ package com.android.server.media; +import android.annotation.NonNull; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.media.AudioAttributes; +import android.media.AudioDeviceAttributes; import android.media.AudioManager; import android.media.MediaRoute2Info; import android.media.MediaRoute2ProviderInfo; @@ -37,6 +40,7 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; +import java.util.List; import java.util.Objects; /** @@ -71,6 +75,26 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { private final AudioManagerBroadcastReceiver mAudioReceiver = new AudioManagerBroadcastReceiver(); + private final AudioManager.OnDevicesForAttributesChangedListener + mOnDevicesForAttributesChangedListener = + new AudioManager.OnDevicesForAttributesChangedListener() { + @Override + public void onDevicesForAttributesChanged(@NonNull AudioAttributes attributes, + @NonNull List devices) { + if (attributes.getUsage() != AudioAttributes.USAGE_MEDIA) { + return; + } + + mHandler.post(() -> { + updateSelectedAudioDevice(devices); + notifyProviderState(); + if (updateSessionInfosIfNeeded()) { + notifySessionInfoUpdated(); + } + }); + } + }; + private final Object mRequestLock = new Object(); @GuardedBy("mRequestLock") private volatile SessionCreationRequest mPendingSessionCreationRequest; @@ -100,8 +124,15 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { }); }); + mAudioManager.addOnDevicesForAttributesChangedListener( + AudioAttributesUtils.ATTRIBUTES_MEDIA, mContext.getMainExecutor(), + mOnDevicesForAttributesChangedListener); + // These methods below should be called after all fields are initialized, as they // access the fields inside. + List devices = + mAudioManager.getDevicesForAttributes(AudioAttributesUtils.ATTRIBUTES_MEDIA); + updateSelectedAudioDevice(devices); updateProviderState(); updateSessionInfosIfNeeded(); } @@ -239,6 +270,26 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { } } + private void updateSelectedAudioDevice(@NonNull List devices) { + if (devices.isEmpty()) { + Slog.w(TAG, "The list of preferred devices was empty."); + return; + } + + AudioDeviceAttributes audioDeviceAttributes = devices.get(0); + + if (AudioAttributesUtils.isDeviceOutputAttributes(audioDeviceAttributes)) { + mDeviceRouteController.selectRoute( + AudioAttributesUtils.mapToMediaRouteType(audioDeviceAttributes)); + mBluetoothRouteController.selectRoute(null); + } else if (AudioAttributesUtils.isBluetoothOutputAttributes(audioDeviceAttributes)) { + mDeviceRouteController.selectRoute(null); + mBluetoothRouteController.selectRoute(audioDeviceAttributes.getAddress()); + } else { + Slog.w(TAG, "Unknown audio attributes: " + audioDeviceAttributes); + } + } + private void updateProviderState() { MediaRoute2ProviderInfo.Builder builder = new MediaRoute2ProviderInfo.Builder();