Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2ea196af authored by Vlad Popa's avatar Vlad Popa
Browse files

CSD: Add possibility to select the BT audio device type

Extended the AdiDeviceState with properties to store the bt audio device
type. As part of the AdiDeviceState these properties will be persisted
with the help of the Secure Settings.

In case a bt device was categorized as headphone/non-headphone device we
send this information to the audioserver which will start/stop the sound
dose computation.

Test: manually changing bt device type
Bug: 287011781

Change-Id: Ia54e141a09b7c95f43b59bab1445eb08890383b3
parent b024dffb
Loading
Loading
Loading
Loading
+108 −0
Original line number Diff line number Diff line
@@ -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
+6 −0
Original line number Diff line number Diff line
@@ -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();
+43 −8
Original line number Diff line number Diff line
@@ -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);
    }

}
+20 −9
Original line number Diff line number Diff line
@@ -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() {
+91 −25
Original line number Diff line number Diff line
@@ -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) {
            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();
@@ -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,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();
@@ -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