Loading media/java/android/media/AudioManager.java +108 −0 Original line number Diff line number Diff line Loading @@ -6927,6 +6927,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 Loading media/java/android/media/IAudioService.aidl +6 −0 Original line number Diff line number Diff line Loading @@ -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(); Loading services/core/java/com/android/server/audio/AdiDeviceState.java +43 −8 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading @@ -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 Loading Loading @@ -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) { Loading @@ -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() { Loading @@ -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()); } Loading @@ -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); Loading @@ -200,5 +236,4 @@ import java.util.Objects; return new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT, mDeviceType, mDeviceAddress); } } services/core/java/com/android/server/audio/AudioDeviceBroker.java +20 −9 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); } } Loading @@ -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 Loading @@ -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); } } Loading @@ -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() { Loading services/core/java/com/android/server/audio/AudioDeviceInventory.java +91 −25 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading @@ -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) { return List.copyOf(mDeviceInventory); 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) { 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(); Loading Loading @@ -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"); } } Loading Loading @@ -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(); Loading Loading @@ -2423,20 +2487,21 @@ 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); } 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(); } } /*package*/ void setDeviceSettings(String settings) { clearDeviceInventory(); Loading @@ -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); } } } Loading Loading
media/java/android/media/AudioManager.java +108 −0 Original line number Diff line number Diff line Loading @@ -6927,6 +6927,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 Loading
media/java/android/media/IAudioService.aidl +6 −0 Original line number Diff line number Diff line Loading @@ -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(); Loading
services/core/java/com/android/server/audio/AdiDeviceState.java +43 −8 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading @@ -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 Loading Loading @@ -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) { Loading @@ -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() { Loading @@ -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()); } Loading @@ -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); Loading @@ -200,5 +236,4 @@ import java.util.Objects; return new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT, mDeviceType, mDeviceAddress); } }
services/core/java/com/android/server/audio/AudioDeviceBroker.java +20 −9 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); } } Loading @@ -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 Loading @@ -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); } } Loading @@ -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() { Loading
services/core/java/com/android/server/audio/AudioDeviceInventory.java +91 −25 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading @@ -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) { return List.copyOf(mDeviceInventory); 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) { 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(); Loading Loading @@ -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"); } } Loading Loading @@ -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(); Loading Loading @@ -2423,20 +2487,21 @@ 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); } 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(); } } /*package*/ void setDeviceSettings(String settings) { clearDeviceInventory(); Loading @@ -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); } } } Loading