summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--media/java/android/media/AudioManager.java108
-rw-r--r--media/java/android/media/IAudioService.aidl6
-rw-r--r--services/core/java/com/android/server/audio/AdiDeviceState.java51
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceBroker.java29
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceInventory.java116
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java71
-rw-r--r--services/core/java/com/android/server/audio/SoundDoseHelper.java54
-rw-r--r--services/core/java/com/android/server/audio/SpatializerHelper.java4
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.
}