diff options
8 files changed, 393 insertions, 46 deletions
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 4759689335e9..e8c9d0dbd884 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -6929,6 +6929,114 @@ public class AudioManager { /** * @hide + * Describes an audio device that has not been categorized with a specific + * audio type. + */ + public static final int AUDIO_DEVICE_CATEGORY_UNKNOWN = 0; + + /** + * @hide + * Describes an audio device which is categorized as something different. + */ + public static final int AUDIO_DEVICE_CATEGORY_OTHER = 1; + + /** + * @hide + * Describes an audio device which was categorized as speakers. + */ + public static final int AUDIO_DEVICE_CATEGORY_SPEAKER = 2; + + /** + * @hide + * Describes an audio device which was categorized as headphones. + */ + public static final int AUDIO_DEVICE_CATEGORY_HEADPHONES = 3; + + /** + * @hide + * Describes an audio device which was categorized as car-kit. + */ + public static final int AUDIO_DEVICE_CATEGORY_CARKIT = 4; + + /** + * @hide + * Describes an audio device which was categorized as watch. + */ + public static final int AUDIO_DEVICE_CATEGORY_WATCH = 5; + + /** + * @hide + * Describes an audio device which was categorized as hearing aid. + */ + public static final int AUDIO_DEVICE_CATEGORY_HEARING_AID = 6; + + /** + * @hide + * Describes an audio device which was categorized as receiver. + */ + public static final int AUDIO_DEVICE_CATEGORY_RECEIVER = 7; + + /** @hide */ + @IntDef(flag = false, prefix = "AUDIO_DEVICE_CATEGORY", value = { + AUDIO_DEVICE_CATEGORY_UNKNOWN, + AUDIO_DEVICE_CATEGORY_OTHER, + AUDIO_DEVICE_CATEGORY_SPEAKER, + AUDIO_DEVICE_CATEGORY_HEADPHONES, + AUDIO_DEVICE_CATEGORY_CARKIT, + AUDIO_DEVICE_CATEGORY_WATCH, + AUDIO_DEVICE_CATEGORY_HEARING_AID, + AUDIO_DEVICE_CATEGORY_RECEIVER } + ) + @Retention(RetentionPolicy.SOURCE) + public @interface AudioDeviceCategory {} + + /** @hide */ + public static String audioDeviceCategoryToString(int audioDeviceCategory) { + switch (audioDeviceCategory) { + case AUDIO_DEVICE_CATEGORY_UNKNOWN: return "AUDIO_DEVICE_CATEGORY_UNKNOWN"; + case AUDIO_DEVICE_CATEGORY_OTHER: return "AUDIO_DEVICE_CATEGORY_OTHER"; + case AUDIO_DEVICE_CATEGORY_SPEAKER: return "AUDIO_DEVICE_CATEGORY_SPEAKER"; + case AUDIO_DEVICE_CATEGORY_HEADPHONES: return "AUDIO_DEVICE_CATEGORY_HEADPHONES"; + case AUDIO_DEVICE_CATEGORY_CARKIT: return "AUDIO_DEVICE_CATEGORY_CARKIT"; + case AUDIO_DEVICE_CATEGORY_WATCH: return "AUDIO_DEVICE_CATEGORY_WATCH"; + case AUDIO_DEVICE_CATEGORY_HEARING_AID: return "AUDIO_DEVICE_CATEGORY_HEARING_AID"; + case AUDIO_DEVICE_CATEGORY_RECEIVER: return "AUDIO_DEVICE_CATEGORY_RECEIVER"; + default: + return new StringBuilder("unknown AudioDeviceCategory ").append( + audioDeviceCategory).toString(); + } + } + + /** + * @hide + * Sets the audio device type of a Bluetooth device given its MAC address + */ + @RequiresPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) + public void setBluetoothAudioDeviceCategory(@NonNull String address, boolean isBle, + @AudioDeviceCategory int btAudioDeviceType) { + try { + getService().setBluetoothAudioDeviceCategory(address, isBle, btAudioDeviceType); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @hide + * Gets the audio device type of a Bluetooth device given its MAC address + */ + @RequiresPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) + @AudioDeviceCategory + public int getBluetoothAudioDeviceCategory(@NonNull String address, boolean isBle) { + try { + return getService().getBluetoothAudioDeviceCategory(address, isBle); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @hide * Sound dose warning at every 100% of dose during integration window */ public static final int CSD_WARNING_DOSE_REACHED_1X = 1; diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index 02f765a3dab9..b2466e990b8f 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -324,6 +324,12 @@ interface IAudioService { @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED") boolean isCsdEnabled(); + @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED") + oneway void setBluetoothAudioDeviceCategory(in String address, boolean isBle, int deviceType); + + @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED") + int getBluetoothAudioDeviceCategory(in String address, boolean isBle); + int setHdmiSystemAudioSupported(boolean on); boolean isHdmiSystemAudioSupported(); diff --git a/services/core/java/com/android/server/audio/AdiDeviceState.java b/services/core/java/com/android/server/audio/AdiDeviceState.java index 683b3eb7a92e..247094f2f796 100644 --- a/services/core/java/com/android/server/audio/AdiDeviceState.java +++ b/services/core/java/com/android/server/audio/AdiDeviceState.java @@ -16,6 +16,7 @@ package com.android.server.audio; +import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_UNKNOWN; import static android.media.AudioSystem.DEVICE_NONE; import static android.media.AudioSystem.isBluetoothDevice; @@ -23,8 +24,10 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.media.AudioDeviceAttributes; import android.media.AudioDeviceInfo; +import android.media.AudioManager; import android.text.TextUtils; import android.util.Log; +import android.util.Pair; import java.util.Objects; @@ -41,8 +44,16 @@ import java.util.Objects; private final int mDeviceType; private final int mInternalDeviceType; + @NonNull private final String mDeviceAddress; + + /** Unique device id from internal device type and address. */ + private final Pair<Integer, String> mDeviceId; + + @AudioManager.AudioDeviceCategory + private int mAudioDeviceCategory = AUDIO_DEVICE_CATEGORY_UNKNOWN; + private boolean mSAEnabled; private boolean mHasHeadTracker = false; private boolean mHeadTrackerEnabled; @@ -68,6 +79,12 @@ import java.util.Objects; } mDeviceAddress = isBluetoothDevice(mInternalDeviceType) ? Objects.requireNonNull( address) : ""; + + mDeviceId = new Pair<>(mInternalDeviceType, mDeviceAddress); + } + + public Pair<Integer, String> getDeviceId() { + return mDeviceId; } @AudioDeviceInfo.AudioDeviceType @@ -109,6 +126,15 @@ import java.util.Objects; return mHasHeadTracker; } + @AudioDeviceInfo.AudioDeviceType + public int getAudioDeviceCategory() { + return mAudioDeviceCategory; + } + + public void setAudioDeviceCategory(@AudioDeviceInfo.AudioDeviceType int audioDeviceCategory) { + mAudioDeviceCategory = audioDeviceCategory; + } + @Override public boolean equals(Object obj) { if (this == obj) { @@ -127,20 +153,23 @@ import java.util.Objects; && mDeviceAddress.equals(sads.mDeviceAddress) // NonNull && mSAEnabled == sads.mSAEnabled && mHasHeadTracker == sads.mHasHeadTracker - && mHeadTrackerEnabled == sads.mHeadTrackerEnabled; + && mHeadTrackerEnabled == sads.mHeadTrackerEnabled + && mAudioDeviceCategory == sads.mAudioDeviceCategory; } @Override public int hashCode() { return Objects.hash(mDeviceType, mInternalDeviceType, mDeviceAddress, mSAEnabled, - mHasHeadTracker, mHeadTrackerEnabled); + mHasHeadTracker, mHeadTrackerEnabled, mAudioDeviceCategory); } @Override public String toString() { return "type: " + mDeviceType + "internal type: " + mInternalDeviceType - + " addr: " + mDeviceAddress + " enabled: " + mSAEnabled - + " HT: " + mHasHeadTracker + " HTenabled: " + mHeadTrackerEnabled; + + " addr: " + mDeviceAddress + " bt audio type: " + + AudioManager.audioDeviceCategoryToString(mAudioDeviceCategory) + + " enabled: " + mSAEnabled + " HT: " + mHasHeadTracker + + " HTenabled: " + mHeadTrackerEnabled; } public String toPersistableString() { @@ -150,6 +179,7 @@ import java.util.Objects; .append(SETTING_FIELD_SEPARATOR).append(mHasHeadTracker ? "1" : "0") .append(SETTING_FIELD_SEPARATOR).append(mHeadTrackerEnabled ? "1" : "0") .append(SETTING_FIELD_SEPARATOR).append(mInternalDeviceType) + .append(SETTING_FIELD_SEPARATOR).append(mAudioDeviceCategory) .toString()); } @@ -174,21 +204,27 @@ import java.util.Objects; String[] fields = TextUtils.split(persistedString, SETTING_FIELD_SEPARATOR); // we may have 5 fields for the legacy AdiDeviceState and 6 containing the internal // device type - if (fields.length != 5 && fields.length != 6) { - // expecting all fields, fewer may mean corruption, ignore those settings + if (fields.length < 5 || fields.length > 7) { + // different number of fields may mean corruption, ignore those settings + // newly added fields are optional (mInternalDeviceType, mBtAudioDeviceCategory) return null; } try { final int deviceType = Integer.parseInt(fields[0]); int internalDeviceType = -1; - if (fields.length == 6) { + if (fields.length >= 6) { internalDeviceType = Integer.parseInt(fields[5]); } + int audioDeviceCategory = AUDIO_DEVICE_CATEGORY_UNKNOWN; + if (fields.length == 7) { + audioDeviceCategory = Integer.parseInt(fields[6]); + } final AdiDeviceState deviceState = new AdiDeviceState(deviceType, internalDeviceType, fields[1]); deviceState.setHasHeadTracker(Integer.parseInt(fields[2]) == 1); deviceState.setHasHeadTracker(Integer.parseInt(fields[3]) == 1); deviceState.setHeadTrackerEnabled(Integer.parseInt(fields[4]) == 1); + deviceState.setAudioDeviceCategory(audioDeviceCategory); return deviceState; } catch (NumberFormatException e) { Log.e(TAG, "unable to parse setting for AdiDeviceState: " + persistedString, e); @@ -200,5 +236,4 @@ import java.util.Objects; return new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT, mDeviceType, mDeviceAddress); } - } diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index 946f01688e66..af8aa9167217 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -63,6 +63,7 @@ import com.android.server.utils.EventLogger; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.HashSet; import java.util.LinkedList; import java.util.List; @@ -2493,13 +2494,13 @@ public class AudioDeviceBroker { void onPersistAudioDeviceSettings() { final String deviceSettings = mDeviceInventory.getDeviceSettings(); - Log.v(TAG, "saving audio device settings: " + deviceSettings); + Log.v(TAG, "saving AdiDeviceState: " + deviceSettings); final SettingsAdapter settings = mAudioService.getSettings(); boolean res = settings.putSecureStringForUser(mAudioService.getContentResolver(), Settings.Secure.AUDIO_DEVICE_INVENTORY, deviceSettings, UserHandle.USER_CURRENT); if (!res) { - Log.e(TAG, "error saving audio device settings: " + deviceSettings); + Log.e(TAG, "error saving AdiDeviceState: " + deviceSettings); } } @@ -2509,7 +2510,7 @@ public class AudioDeviceBroker { String settings = settingsAdapter.getSecureStringForUser(contentResolver, Settings.Secure.AUDIO_DEVICE_INVENTORY, UserHandle.USER_CURRENT); if (settings == null) { - Log.i(TAG, "reading spatial audio device settings from legacy key" + Log.i(TAG, "reading AdiDeviceState from legacy key" + Settings.Secure.SPATIAL_AUDIO_ENABLED); // legacy string format for key SPATIAL_AUDIO_ENABLED has the same order of fields like // the strings for key AUDIO_DEVICE_INVENTORY. This will ensure to construct valid @@ -2517,21 +2518,21 @@ public class AudioDeviceBroker { settings = settingsAdapter.getSecureStringForUser(contentResolver, Settings.Secure.SPATIAL_AUDIO_ENABLED, UserHandle.USER_CURRENT); if (settings == null) { - Log.i(TAG, "no spatial audio device settings stored with legacy key"); + Log.i(TAG, "no AdiDeviceState stored with legacy key"); } else if (!settings.equals("")) { // Delete old key value and update the new key if (!settingsAdapter.putSecureStringForUser(contentResolver, Settings.Secure.SPATIAL_AUDIO_ENABLED, /*value=*/"", UserHandle.USER_CURRENT)) { - Log.w(TAG, "cannot erase the legacy audio device settings with key " + Log.w(TAG, "cannot erase the legacy AdiDeviceState with key " + Settings.Secure.SPATIAL_AUDIO_ENABLED); } if (!settingsAdapter.putSecureStringForUser(contentResolver, Settings.Secure.AUDIO_DEVICE_INVENTORY, settings, UserHandle.USER_CURRENT)) { - Log.e(TAG, "error updating the new audio device settings with key " + Log.e(TAG, "error updating the new AdiDeviceState with key " + Settings.Secure.AUDIO_DEVICE_INVENTORY); } } @@ -2551,19 +2552,29 @@ public class AudioDeviceBroker { return mDeviceInventory.getDeviceSettings(); } - List<AdiDeviceState> getImmutableDeviceInventory() { + Collection<AdiDeviceState> getImmutableDeviceInventory() { return mDeviceInventory.getImmutableDeviceInventory(); } - void addDeviceStateToInventory(AdiDeviceState deviceState) { - mDeviceInventory.addDeviceStateToInventory(deviceState); + void addOrUpdateDeviceSAStateInInventory(AdiDeviceState deviceState) { + mDeviceInventory.addOrUpdateDeviceSAStateInInventory(deviceState); } + void addOrUpdateBtAudioDeviceCategoryInInventory(AdiDeviceState deviceState) { + mDeviceInventory.addOrUpdateAudioDeviceCategoryInInventory(deviceState); + } + + @Nullable AdiDeviceState findDeviceStateForAudioDeviceAttributes(AudioDeviceAttributes ada, int canonicalType) { return mDeviceInventory.findDeviceStateForAudioDeviceAttributes(ada, canonicalType); } + @Nullable + AdiDeviceState findBtDeviceStateForAddress(String address, boolean isBle) { + return mDeviceInventory.findBtDeviceStateForAddress(address, isBle); + } + //------------------------------------------------ // for testing purposes only void clearDeviceInventory() { diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index f5b7ecf5daf4..5a92cb464950 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -15,6 +15,8 @@ */ package com.android.server.audio; +import static android.media.AudioSystem.DEVICE_OUT_ALL_A2DP_SET; +import static android.media.AudioSystem.DEVICE_OUT_ALL_BLE_SET; import static android.media.AudioSystem.isBluetoothDevice; import android.annotation.NonNull; @@ -61,11 +63,13 @@ import com.google.android.collect.Sets; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; +import java.util.Map.Entry; import java.util.Objects; import java.util.Set; import java.util.UUID; @@ -90,35 +94,95 @@ public class AudioDeviceInventory { private static final String mMetricsId = "audio.device."; private final Object mDeviceInventoryLock = new Object(); - @GuardedBy("mDeviceCatalogLock") - private final ArrayList<AdiDeviceState> mDeviceInventory = new ArrayList<>(0); - List<AdiDeviceState> getImmutableDeviceInventory() { + + @GuardedBy("mDeviceInventoryLock") + private final HashMap<Pair<Integer, String>, AdiDeviceState> mDeviceInventory = new HashMap<>(); + + Collection<AdiDeviceState> getImmutableDeviceInventory() { + synchronized (mDeviceInventoryLock) { + return mDeviceInventory.values(); + } + } + + /** + * Adds a new AdiDeviceState or updates the spatial audio related properties of the matching + * AdiDeviceState in the {@link AudioDeviceInventory#mDeviceInventory} list. + * @param deviceState the device to update + */ + void addOrUpdateDeviceSAStateInInventory(AdiDeviceState deviceState) { + synchronized (mDeviceInventoryLock) { + mDeviceInventory.merge(deviceState.getDeviceId(), deviceState, (oldState, newState) -> { + oldState.setHasHeadTracker(newState.hasHeadTracker()); + oldState.setHeadTrackerEnabled(newState.isHeadTrackerEnabled()); + oldState.setSAEnabled(newState.isSAEnabled()); + return oldState; + }); + } + } + + /** + * Adds a new AdiDeviceState or updates the audio device cateogory of the matching + * AdiDeviceState in the {@link AudioDeviceInventory#mDeviceInventory} list. + * @param deviceState the device to update + */ + void addOrUpdateAudioDeviceCategoryInInventory(AdiDeviceState deviceState) { synchronized (mDeviceInventoryLock) { - return List.copyOf(mDeviceInventory); + mDeviceInventory.merge(deviceState.getDeviceId(), deviceState, (oldState, newState) -> { + oldState.setAudioDeviceCategory(newState.getAudioDeviceCategory()); + return oldState; + }); } } - void addDeviceStateToInventory(AdiDeviceState deviceState) { + /** + * Finds the BT device that matches the passed {@code address}. Currently, this method only + * returns a valid device for A2DP and BLE devices. + * + * @param address MAC address of BT device + * @param isBle true if the device is BLE, false for A2DP + * @return the found {@link AdiDeviceState} or {@code null} otherwise. + */ + @Nullable + AdiDeviceState findBtDeviceStateForAddress(String address, boolean isBle) { synchronized (mDeviceInventoryLock) { - mDeviceInventory.add(deviceState); + final Set<Integer> deviceSet = isBle ? DEVICE_OUT_ALL_BLE_SET : DEVICE_OUT_ALL_A2DP_SET; + for (Integer internalType : deviceSet) { + AdiDeviceState deviceState = mDeviceInventory.get( + new Pair<>(internalType, address)); + if (deviceState != null) { + return deviceState; + } + } } + return null; } + /** + * Finds the device state that matches the passed {@link AudioDeviceAttributes} and device + * type. Note: currently this method only returns a valid device for A2DP and BLE devices. + * + * @param ada attributes of device to match + * @param canonicalDeviceType external device type to match + * @return the found {@link AdiDeviceState} matching a cached A2DP or BLE device or + * {@code null} otherwise. + */ + @Nullable AdiDeviceState findDeviceStateForAudioDeviceAttributes(AudioDeviceAttributes ada, int canonicalDeviceType) { final boolean isWireless = isBluetoothDevice(ada.getInternalType()); synchronized (mDeviceInventoryLock) { - for (AdiDeviceState deviceSetting : mDeviceInventory) { - if (deviceSetting.getDeviceType() == canonicalDeviceType + for (AdiDeviceState deviceState : mDeviceInventory.values()) { + if (deviceState.getDeviceType() == canonicalDeviceType && (!isWireless || ada.getAddress().equals( - deviceSetting.getDeviceAddress()))) { - return deviceSetting; + deviceState.getDeviceAddress()))) { + return deviceState; } } } return null; } + /** Clears all cached {@link AdiDeviceState}'s. */ void clearDeviceInventory() { synchronized (mDeviceInventoryLock) { mDeviceInventory.clear(); @@ -384,7 +448,7 @@ public class AudioDeviceInventory { + " role:" + key.second + " devices:" + devices); }); pw.println("\ndevices:\n"); synchronized (mDeviceInventoryLock) { - for (AdiDeviceState device : mDeviceInventory) { + for (AdiDeviceState device : mDeviceInventory.values()) { pw.println("\t" + device + "\n"); } } @@ -1232,11 +1296,11 @@ public class AudioDeviceInventory { AudioDeviceInfo[] connectedDevices = AudioManager.getDevicesStatic( AudioManager.GET_DEVICES_ALL); - Iterator<Map.Entry<Pair<Integer, Integer>, List<AudioDeviceAttributes>>> itRole = + Iterator<Entry<Pair<Integer, Integer>, List<AudioDeviceAttributes>>> itRole = rolesMap.entrySet().iterator(); while (itRole.hasNext()) { - Map.Entry<Pair<Integer, Integer>, List<AudioDeviceAttributes>> entry = + Entry<Pair<Integer, Integer>, List<AudioDeviceAttributes>> entry = itRole.next(); Pair<Integer, Integer> keyRole = entry.getKey(); Iterator<AudioDeviceAttributes> itDev = rolesMap.get(keyRole).iterator(); @@ -2423,19 +2487,20 @@ public class AudioDeviceInventory { int deviceCatalogSize = 0; synchronized (mDeviceInventoryLock) { deviceCatalogSize = mDeviceInventory.size(); - } - final StringBuilder settingsBuilder = new StringBuilder( - deviceCatalogSize * AdiDeviceState.getPeristedMaxSize()); - synchronized (mDeviceInventoryLock) { - for (int i = 0; i < mDeviceInventory.size(); i++) { - settingsBuilder.append(mDeviceInventory.get(i).toPersistableString()); - if (i != mDeviceInventory.size() - 1) { - settingsBuilder.append(SETTING_DEVICE_SEPARATOR_CHAR); - } + final StringBuilder settingsBuilder = new StringBuilder( + deviceCatalogSize * AdiDeviceState.getPeristedMaxSize()); + + Iterator<AdiDeviceState> iterator = mDeviceInventory.values().iterator(); + if (iterator.hasNext()) { + settingsBuilder.append(iterator.next().toPersistableString()); + } + while (iterator.hasNext()) { + settingsBuilder.append(SETTING_DEVICE_SEPARATOR_CHAR); + settingsBuilder.append(iterator.next().toPersistableString()); } + return settingsBuilder.toString(); } - return settingsBuilder.toString(); } /*package*/ void setDeviceSettings(String settings) { @@ -2448,7 +2513,8 @@ public class AudioDeviceInventory { // Note if the device is not compatible with spatialization mode or the device // type is not canonical, it will be ignored in {@link SpatializerHelper}. if (devState != null) { - addDeviceStateToInventory(devState); + addOrUpdateDeviceSAStateInInventory(devState); + addOrUpdateAudioDeviceCategoryInInventory(devState); } } } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 3353b9ec538f..76c4cfe929bb 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -18,6 +18,14 @@ package com.android.server.audio; import static android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED; import static android.app.BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT; +import static android.media.AudioDeviceInfo.TYPE_BLE_HEADSET; +import static android.media.AudioDeviceInfo.TYPE_BLE_SPEAKER; +import static android.media.AudioDeviceInfo.TYPE_BLUETOOTH_A2DP; +import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES; +import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_UNKNOWN; +import static android.media.AudioManager.DEVICE_OUT_BLE_HEADSET; +import static android.media.AudioManager.DEVICE_OUT_BLE_SPEAKER; +import static android.media.AudioManager.DEVICE_OUT_BLUETOOTH_A2DP; import static android.media.AudioManager.RINGER_MODE_NORMAL; import static android.media.AudioManager.RINGER_MODE_SILENT; import static android.media.AudioManager.RINGER_MODE_VIBRATE; @@ -92,6 +100,7 @@ import android.media.AudioFocusRequest; import android.media.AudioFormat; import android.media.AudioHalVersionInfo; import android.media.AudioManager; +import android.media.AudioManager.AudioDeviceCategory; import android.media.AudioManagerInternal; import android.media.AudioMixerAttributes; import android.media.AudioPlaybackConfiguration; @@ -405,6 +414,7 @@ public class AudioService extends IAudioService.Stub private static final int MSG_DISABLE_AUDIO_FOR_UID = 100; private static final int MSG_INIT_STREAMS_VOLUMES = 101; private static final int MSG_INIT_SPATIALIZER = 102; + private static final int MSG_INIT_ADI_DEVICE_STATES = 103; // end of messages handled under wakelock @@ -1286,6 +1296,8 @@ public class AudioService extends IAudioService.Stub // done with service initialization, continue additional work in our Handler thread queueMsgUnderWakeLock(mAudioHandler, MSG_INIT_STREAMS_VOLUMES, 0 /* arg1 */, 0 /* arg2 */, null /* obj */, 0 /* delay */); + queueMsgUnderWakeLock(mAudioHandler, MSG_INIT_ADI_DEVICE_STATES, + 0 /* arg1 */, 0 /* arg2 */, null /* obj */, 0 /* delay */); queueMsgUnderWakeLock(mAudioHandler, MSG_INIT_SPATIALIZER, 0 /* arg1 */, 0 /* arg2 */, null /* obj */, 0 /* delay */); @@ -7377,7 +7389,7 @@ public class AudioService extends IAudioService.Stub if (pkgName == null) { pkgName = ""; } - if (device.getType() == AudioDeviceInfo.TYPE_BLUETOOTH_A2DP) { + if (device.getType() == TYPE_BLUETOOTH_A2DP) { avrcpSupportsAbsoluteVolume(device.getAddress(), deviceVolumeBehavior == AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE); return; @@ -9253,6 +9265,11 @@ public class AudioService extends IAudioService.Stub mAudioEventWakeLock.release(); break; + case MSG_INIT_ADI_DEVICE_STATES: + onInitAdiDeviceStates(); + mAudioEventWakeLock.release(); + break; + case MSG_INIT_SPATIALIZER: onInitSpatializer(); mAudioEventWakeLock.release(); @@ -10322,8 +10339,13 @@ public class AudioService extends IAudioService.Stub /*arg1*/ 0, /*arg2*/ 0, TAG, /*delay*/ 0); } - void onInitSpatializer() { + void onInitAdiDeviceStates() { mDeviceBroker.onReadAudioDeviceSettings(); + mSoundDoseHelper.initCachedAudioDeviceCategories( + mDeviceBroker.getImmutableDeviceInventory()); + } + + void onInitSpatializer() { mSpatializerHelper.init(/*effectExpected*/ mHasSpatializerEffect); mSpatializerHelper.setFeatureEnabled(mHasSpatializerEffect); } @@ -10718,6 +10740,51 @@ public class AudioService extends IAudioService.Stub return mSoundDoseHelper.isCsdEnabled(); } + @Override + @android.annotation.EnforcePermission(MODIFY_AUDIO_SETTINGS_PRIVILEGED) + public void setBluetoothAudioDeviceCategory(@NonNull String address, boolean isBle, + @AudioDeviceCategory int btAudioDeviceCategory) { + super.setBluetoothAudioDeviceCategory_enforcePermission(); + + final String addr = Objects.requireNonNull(address); + + AdiDeviceState deviceState = mDeviceBroker.findBtDeviceStateForAddress(addr, isBle); + + int internalType = !isBle ? DEVICE_OUT_BLUETOOTH_A2DP + : ((btAudioDeviceCategory == AUDIO_DEVICE_CATEGORY_HEADPHONES) + ? DEVICE_OUT_BLE_HEADSET : DEVICE_OUT_BLE_SPEAKER); + int deviceType = !isBle ? TYPE_BLUETOOTH_A2DP + : ((btAudioDeviceCategory == AUDIO_DEVICE_CATEGORY_HEADPHONES) ? TYPE_BLE_HEADSET + : TYPE_BLE_SPEAKER); + + if (deviceState == null) { + deviceState = new AdiDeviceState(deviceType, internalType, addr); + } + + deviceState.setAudioDeviceCategory(btAudioDeviceCategory); + + mDeviceBroker.addOrUpdateBtAudioDeviceCategoryInInventory(deviceState); + mDeviceBroker.persistAudioDeviceSettings(); + + mSoundDoseHelper.setAudioDeviceCategory(addr, internalType, + btAudioDeviceCategory == AUDIO_DEVICE_CATEGORY_HEADPHONES); + } + + @Override + @android.annotation.EnforcePermission(MODIFY_AUDIO_SETTINGS_PRIVILEGED) + @AudioDeviceCategory + public int getBluetoothAudioDeviceCategory(@NonNull String address, boolean isBle) { + super.getBluetoothAudioDeviceCategory_enforcePermission(); + + final AdiDeviceState deviceState = mDeviceBroker.findBtDeviceStateForAddress( + Objects.requireNonNull(address), isBle); + if (deviceState == null) { + return AUDIO_DEVICE_CATEGORY_UNKNOWN; + } + + return deviceState.getAudioDeviceCategory(); + } + //========================================================================================== // Hdmi CEC: // - System audio mode: diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java index 01af3a838597..851c5c3cb73c 100644 --- a/services/core/java/com/android/server/audio/SoundDoseHelper.java +++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java @@ -16,6 +16,9 @@ package com.android.server.audio; +import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES; +import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_UNKNOWN; + import static com.android.server.audio.AudioService.MAX_STREAM_VOLUME; import static com.android.server.audio.AudioService.MIN_STREAM_VOLUME; import static com.android.server.audio.AudioService.MSG_SET_DEVICE_VOLUME; @@ -57,6 +60,7 @@ import com.android.server.utils.EventLogger; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; @@ -189,6 +193,9 @@ public class SoundDoseHelper { private final AtomicBoolean mEnableCsd = new AtomicBoolean(false); + private ArrayList<ISoundDose.AudioDeviceCategory> mCachedAudioDeviceCategories = + new ArrayList<>(); + private final Object mCsdStateLock = new Object(); private final AtomicReference<ISoundDose> mSoundDose = new AtomicReference<>(); @@ -487,6 +494,43 @@ public class SoundDoseHelper { return false; } + void setAudioDeviceCategory(String address, int internalAudioType, boolean isHeadphone) { + if (!mEnableCsd.get()) { + return; + } + + final ISoundDose soundDose = mSoundDose.get(); + if (soundDose == null) { + Log.w(TAG, "Sound dose interface not initialized"); + return; + } + + try { + final ISoundDose.AudioDeviceCategory audioDeviceCategory = + new ISoundDose.AudioDeviceCategory(); + audioDeviceCategory.address = address; + audioDeviceCategory.internalAudioType = internalAudioType; + audioDeviceCategory.csdCompatible = isHeadphone; + soundDose.setAudioDeviceCategory(audioDeviceCategory); + } catch (RemoteException e) { + Log.e(TAG, "Exception while forcing the internal MEL computation", e); + } + } + + void initCachedAudioDeviceCategories(Collection<AdiDeviceState> deviceStates) { + for (final AdiDeviceState state : deviceStates) { + if (state.getAudioDeviceCategory() != AUDIO_DEVICE_CATEGORY_UNKNOWN) { + final ISoundDose.AudioDeviceCategory audioDeviceCategory = + new ISoundDose.AudioDeviceCategory(); + audioDeviceCategory.address = state.getDeviceAddress(); + audioDeviceCategory.internalAudioType = state.getInternalDeviceType(); + audioDeviceCategory.csdCompatible = + state.getAudioDeviceCategory() == AUDIO_DEVICE_CATEGORY_HEADPHONES; + mCachedAudioDeviceCategories.add(audioDeviceCategory); + } + } + } + /*package*/ int safeMediaVolumeIndex(int device) { final int vol = mSafeMediaVolumeDevices.get(device); if (vol == SAFE_MEDIA_VOLUME_UNINITIALIZED) { @@ -810,6 +854,16 @@ public class SoundDoseHelper { Log.v(TAG, "Initializing sound dose"); + try { + if (mCachedAudioDeviceCategories.size() > 0) { + soundDose.initCachedAudioDeviceCategories(mCachedAudioDeviceCategories.toArray( + new ISoundDose.AudioDeviceCategory[0])); + mCachedAudioDeviceCategories.clear(); + } + } catch (RemoteException e) { + Log.e(TAG, "Exception while forcing the internal MEL computation", e); + } + synchronized (mCsdStateLock) { if (mGlobalTimeOffsetInSecs == GLOBAL_TIME_OFFSET_UNINITIALIZED) { mGlobalTimeOffsetInSecs = System.currentTimeMillis() / 1000L; diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java index 969dd60a8012..496bdf48b5bf 100644 --- a/services/core/java/com/android/server/audio/SpatializerHelper.java +++ b/services/core/java/com/android/server/audio/SpatializerHelper.java @@ -560,7 +560,7 @@ public class SpatializerHelper { updatedDevice = new AdiDeviceState(canonicalDeviceType, ada.getInternalType(), ada.getAddress()); initSAState(updatedDevice); - mDeviceBroker.addDeviceStateToInventory(updatedDevice); + mDeviceBroker.addOrUpdateDeviceSAStateInInventory(updatedDevice); } if (updatedDevice != null) { onRoutingUpdated(); @@ -693,7 +693,7 @@ public class SpatializerHelper { new AdiDeviceState(canonicalDeviceType, ada.getInternalType(), ada.getAddress()); initSAState(deviceState); - mDeviceBroker.addDeviceStateToInventory(deviceState); + mDeviceBroker.addOrUpdateDeviceSAStateInInventory(deviceState); mDeviceBroker.persistAudioDeviceSettings(); logDeviceState(deviceState, "addWirelessDeviceIfNew"); // may be updated later. } |