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

Commit 0ad136e4 authored by Vlad Popa's avatar Vlad Popa Committed by Android (Google) Code Review
Browse files

Merge changes I6941d65b,Iaf78ff78 into udc-dev

* changes:
  CSD: add timeout of 20h after momentary exposure
  CSD: Implement logic for initial safe hearing warning
parents 3fab5c25 976d08de
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -1315,7 +1315,7 @@ public class AudioService extends IAudioService.Stub
        // persistent data
        initVolumeGroupStates();
        mSoundDoseHelper.initSafeUsbMediaVolumeIndex();
        mSoundDoseHelper.initSafeMediaVolumeIndex();
        // Link VGS on VSS
        initVolumeStreamStates();
@@ -8837,7 +8837,7 @@ public class AudioService extends IAudioService.Stub
        final VolumeStreamState streamState = mStreamStates[update.mStreamType];
        if (update.hasVolumeIndex()) {
            int index = update.getVolumeIndex();
            if (!mSoundDoseHelper.checkSafeMediaVolume(update.mStreamType, index, update.mDevice)) {
            if (mSoundDoseHelper.checkSafeMediaVolume(update.mStreamType, index, update.mDevice)) {
                index = mSoundDoseHelper.safeMediaVolumeIndex(update.mDevice);
            }
            streamState.setIndex(index, update.mDevice, update.mCaller,
+133 −83
Original line number Diff line number Diff line
@@ -52,10 +52,9 @@ import com.android.server.utils.EventLogger;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

@@ -80,7 +79,6 @@ public class SoundDoseHelper {
    // SAFE_MEDIA_VOLUME_DISABLED according to country option. If not SAFE_MEDIA_VOLUME_DISABLED, it
    // can be set to SAFE_MEDIA_VOLUME_INACTIVE by calling AudioService.disableSafeMediaVolume()
    // (when user opts out).
    // Note: when CSD calculation is enabled the state is set to SAFE_MEDIA_VOLUME_DISABLED
    private static final int SAFE_MEDIA_VOLUME_NOT_CONFIGURED = 0;
    private static final int SAFE_MEDIA_VOLUME_DISABLED = 1;
    private static final int SAFE_MEDIA_VOLUME_INACTIVE = 2;  // confirmed
@@ -94,6 +92,8 @@ public class SoundDoseHelper {

    private static final int UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX = (20 * 3600 * 1000); // 20 hours

    private static final int MOMENTARY_EXPOSURE_TIMEOUT_MS = (20 * 3600 * 1000); // 20 hours

    // 30s after boot completed
    private static final int SAFE_VOLUME_CONFIGURE_TIMEOUT_MS = 30000;

@@ -127,30 +127,50 @@ public class SoundDoseHelper {

    // mSafeMediaVolumeIndex is the cached value of config_safe_media_volume_index property
    private int mSafeMediaVolumeIndex;
    // mSafeUsbMediaVolumeDbfs is the cached value of the config_safe_media_volume_usb_mB
    // mSafeMediaVolumeDbfs is the cached value of the config_safe_media_volume_usb_mB
    // property, divided by 100.0.
    private float mSafeUsbMediaVolumeDbfs;

    // mSafeUsbMediaVolumeIndex is used for USB Headsets and is the music volume UI index
    // corresponding to a gain of mSafeUsbMediaVolumeDbfs (defaulting to -37dB) in audio
    // flinger mixer.
    // We remove -22 dBs from the theoretical -15dB to account for the EQ + bass boost
    // amplification when both effects are on with all band gains at maximum.
    // This level corresponds to a loudness of 85 dB SPL for the warning to be displayed when
    // the headset is compliant to EN 60950 with a max loudness of 100dB SPL.
    private int mSafeUsbMediaVolumeIndex;
    // mSafeMediaVolumeDevices lists the devices for which safe media volume is enforced,
    private final Set<Integer> mSafeMediaVolumeDevices = new HashSet<>(
            Arrays.asList(AudioSystem.DEVICE_OUT_WIRED_HEADSET,
                    AudioSystem.DEVICE_OUT_WIRED_HEADPHONE, AudioSystem.DEVICE_OUT_USB_HEADSET));

    private final Set<Integer> mSafeMediaCsdDevices = new HashSet<>(
            Arrays.asList(AudioSystem.DEVICE_OUT_WIRED_HEADSET,
                    AudioSystem.DEVICE_OUT_WIRED_HEADPHONE, AudioSystem.DEVICE_OUT_USB_HEADSET,
                    AudioSystem.DEVICE_OUT_BLE_HEADSET, AudioSystem.DEVICE_OUT_BLE_BROADCAST,
                    AudioSystem.DEVICE_OUT_HEARING_AID,
                    AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES,
                    AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP));
    // For now using the same value for CSD supported devices
    private float mSafeMediaVolumeDbfs;

    private static class SafeDeviceVolumeInfo {
        int mDeviceType;
        int mSafeVolumeIndex = -1;

        SafeDeviceVolumeInfo(int deviceType) {
            mDeviceType = deviceType;
        }
    }

    /**
     * mSafeMediaVolumeDevices lists the devices for which safe media volume is enforced.
     * Contains a safe volume index for a given device type.
     * Indexes are used for headsets and is the music volume UI index
     * corresponding to a gain of mSafeMediaVolumeDbfs (defaulting to -37dB) in audio
     * flinger mixer.
     * We remove -22 dBs from the theoretical -15dB to account for the EQ + bass boost
     * amplification when both effects are on with all band gains at maximum.
     * This level corresponds to a loudness of 85 dB SPL for the warning to be displayed when
     * the headset is compliant to EN 60950 with a max loudness of 100dB SPL.
     */
    private final HashMap<Integer, SafeDeviceVolumeInfo> mSafeMediaVolumeDevices =
            new HashMap<>() {{
                put(AudioSystem.DEVICE_OUT_WIRED_HEADSET,
                        new SafeDeviceVolumeInfo(AudioSystem.DEVICE_OUT_WIRED_HEADSET));
                put(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE,
                        new SafeDeviceVolumeInfo(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE));
                put(AudioSystem.DEVICE_OUT_USB_HEADSET,
                        new SafeDeviceVolumeInfo(AudioSystem.DEVICE_OUT_USB_HEADSET));
                put(AudioSystem.DEVICE_OUT_BLE_HEADSET,
                        new SafeDeviceVolumeInfo(AudioSystem.DEVICE_OUT_BLE_HEADSET));
                put(AudioSystem.DEVICE_OUT_BLE_BROADCAST,
                        new SafeDeviceVolumeInfo(AudioSystem.DEVICE_OUT_BLE_BROADCAST));
                put(AudioSystem.DEVICE_OUT_HEARING_AID,
                        new SafeDeviceVolumeInfo(AudioSystem.DEVICE_OUT_HEARING_AID));
                put(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES,
                        new SafeDeviceVolumeInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES));
                put(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
                        new SafeDeviceVolumeInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP));
            }};

    // mMusicActiveMs is the cumulative time of music activity since safe volume was disabled.
    // When this time reaches UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX, the safe media volume is re-enabled
@@ -173,6 +193,10 @@ public class SoundDoseHelper {

    @GuardedBy("mCsdStateLock")
    private float mCurrentCsd = 0.f;

    @GuardedBy("mCsdStateLock")
    private long mLastMomentaryExposureTimeMs = -1;

    // dose at which the next dose reached warning occurs
    @GuardedBy("mCsdStateLock")
    private float mNextCsdWarning = 1.0f;
@@ -188,10 +212,26 @@ public class SoundDoseHelper {

    private final ISoundDoseCallback.Stub mSoundDoseCallback = new ISoundDoseCallback.Stub() {
        public void onMomentaryExposure(float currentMel, int deviceId) {
            if (!mEnableCsd) {
                Log.w(TAG, "onMomentaryExposure: csd not supported, ignoring callback");
                return;
            }

            Log.w(TAG, "DeviceId " + deviceId + " triggered momentary exposure with value: "
                    + currentMel);
            mLogger.enqueue(SoundDoseEvent.getMomentaryExposureEvent(currentMel));
            if (mEnableCsd) {

            boolean postWarning = false;
            synchronized (mCsdStateLock) {
                if (mLastMomentaryExposureTimeMs < 0
                        || (System.currentTimeMillis() - mLastMomentaryExposureTimeMs)
                        >= MOMENTARY_EXPOSURE_TIMEOUT_MS) {
                    mLastMomentaryExposureTimeMs = System.currentTimeMillis();
                    postWarning = true;
                }
            }

            if (postWarning) {
                mVolumeController.postDisplayCsdWarning(
                        AudioManager.CSD_WARNING_MOMENTARY_EXPOSURE,
                        getTimeoutMsForWarning(AudioManager.CSD_WARNING_MOMENTARY_EXPOSURE));
@@ -250,14 +290,10 @@ public class SoundDoseHelper {
        mContext = context;

        mEnableCsd = mContext.getResources().getBoolean(R.bool.config_audio_csd_enabled_default);
        if (mEnableCsd) {
            mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_DISABLED;
        } else {
        initCsd();

        mSafeMediaVolumeState = mSettings.getGlobalInt(audioService.getContentResolver(),
                Settings.Global.AUDIO_SAFE_VOLUME_STATE, SAFE_MEDIA_VOLUME_NOT_CONFIGURED);
        }

        initCsd();

        // The default safe volume index read here will be replaced by the actual value when
        // the mcc is read by onConfigureSafeMedia()
@@ -388,14 +424,12 @@ public class SoundDoseHelper {
    }

    /*package*/ int safeMediaVolumeIndex(int device) {
        if (!mSafeMediaVolumeDevices.contains(device)) {
        final SafeDeviceVolumeInfo vi = mSafeMediaVolumeDevices.get(device);
        if (vi == null) {
            return MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC];
        }
        if (device == AudioSystem.DEVICE_OUT_USB_HEADSET) {
            return mSafeUsbMediaVolumeIndex;
        } else {
            return mSafeMediaVolumeIndex;
        }

        return vi.mSafeVolumeIndex;
    }

    /*package*/ void restoreMusicActiveMs() {
@@ -419,20 +453,24 @@ public class SoundDoseHelper {
    /*package*/ void enforceSafeMediaVolume(String caller) {
        AudioService.VolumeStreamState streamState = mAudioService.getVssVolumeForStream(
                AudioSystem.STREAM_MUSIC);
        Set<Integer> devices = mSafeMediaVolumeDevices;

        for (int device : devices) {
            int index = streamState.getIndex(device);
            int safeIndex = safeMediaVolumeIndex(device);
        for (SafeDeviceVolumeInfo vi : mSafeMediaVolumeDevices.values()) {
            int index = streamState.getIndex(vi.mDeviceType);
            int safeIndex = safeMediaVolumeIndex(vi.mDeviceType);
            if (index > safeIndex) {
                streamState.setIndex(safeIndex, device, caller, true /*hasModifyAudioSettings*/);
                streamState.setIndex(safeIndex, vi.mDeviceType, caller,
                        true /*hasModifyAudioSettings*/);
                mAudioHandler.sendMessageAtTime(
                        mAudioHandler.obtainMessage(MSG_SET_DEVICE_VOLUME, device, /*arg2=*/0,
                                streamState), /*delay=*/0);
                        mAudioHandler.obtainMessage(MSG_SET_DEVICE_VOLUME, vi.mDeviceType,
                                /*arg2=*/0, streamState), /*delay=*/0);
            }
        }
    }

    /**
     * Returns {@code true} if the safe media actions can be applied for the given stream type,
     * volume index and device.
     **/
    /*package*/ boolean checkSafeMediaVolume(int streamType, int index, int device) {
        boolean result;
        synchronized (mSafeMediaVolumeStateLock) {
@@ -443,17 +481,16 @@ public class SoundDoseHelper {

    @GuardedBy("mSafeMediaVolumeStateLock")
    private boolean checkSafeMediaVolume_l(int streamType, int index, int device) {
        return (mSafeMediaVolumeState != SAFE_MEDIA_VOLUME_ACTIVE)
                    || (AudioService.mStreamVolumeAlias[streamType] != AudioSystem.STREAM_MUSIC)
                    || (!mSafeMediaVolumeDevices.contains(device))
                    || (index <= safeMediaVolumeIndex(device))
                    || mEnableCsd;
        return (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE)
                    && (AudioService.mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC)
                    && (mSafeMediaVolumeDevices.containsKey(device))
                    && (index > safeMediaVolumeIndex(device));
    }

    /*package*/ boolean willDisplayWarningAfterCheckVolume(int streamType, int index, int device,
            int flags) {
        synchronized (mSafeMediaVolumeStateLock) {
            if (!checkSafeMediaVolume_l(streamType, index, device)) {
            if (checkSafeMediaVolume_l(streamType, index, device)) {
                mVolumeController.postDisplaySafeVolumeWarning(flags);
                mPendingVolumeCommand = new StreamVolumeCommand(
                        streamType, index, flags, device);
@@ -484,7 +521,6 @@ public class SoundDoseHelper {
    /*package*/ void scheduleMusicActiveCheck() {
        synchronized (mSafeMediaVolumeStateLock) {
            cancelMusicActiveCheck();
            if (!mEnableCsd) {
            mMusicActiveIntent = PendingIntent.getBroadcast(mContext,
                    REQUEST_CODE_CHECK_MUSIC_ACTIVE,
                    new Intent(ACTION_CHECK_MUSIC_ACTIVE),
@@ -494,13 +530,12 @@ public class SoundDoseHelper {
                            + MUSIC_ACTIVE_POLL_PERIOD_MS, mMusicActiveIntent);
        }
    }
    }

    /*package*/ void onCheckMusicActive(String caller, boolean isStreamActive) {
        synchronized (mSafeMediaVolumeStateLock) {
            if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_INACTIVE) {
                int device = mAudioService.getDeviceForStream(AudioSystem.STREAM_MUSIC);
                if (mSafeMediaVolumeDevices.contains(device) && isStreamActive) {
                if (mSafeMediaVolumeDevices.containsKey(device) && isStreamActive) {
                    scheduleMusicActiveCheck();
                    int index = mAudioService.getVssVolumeForDevice(AudioSystem.STREAM_MUSIC,
                            device);
@@ -528,27 +563,31 @@ public class SoundDoseHelper {

    /*package*/ void configureSafeMedia(boolean forced, String caller) {
        int msg = MSG_CONFIGURE_SAFE_MEDIA;
        if (forced) {
            // unforced should not cancel forced configure messages
            mAudioHandler.removeMessages(msg);
        }

        long time = 0;
        if (forced) {
            time = (SystemClock.uptimeMillis() + (SystemProperties.getBoolean(
                    "audio.safemedia.bypass", false) ? 0 : SAFE_VOLUME_CONFIGURE_TIMEOUT_MS));
        }

        mAudioHandler.sendMessageAtTime(
                mAudioHandler.obtainMessage(msg, /*arg1=*/forced ? 1 : 0, /*arg2=*/0, caller),
                time);
    }

    /*package*/ void initSafeUsbMediaVolumeIndex() {
        // mSafeUsbMediaVolumeIndex must be initialized after createStreamStates() because it
        // relies on audio policy having correct ranges for volume indexes.
        mSafeUsbMediaVolumeIndex = getSafeUsbMediaVolumeIndex();
    /*package*/ void initSafeMediaVolumeIndex() {
        for (SafeDeviceVolumeInfo vi : mSafeMediaVolumeDevices.values()) {
            vi.mSafeVolumeIndex = getSafeDeviceMediaVolumeIndex(vi.mDeviceType);
        }
    }

    /*package*/ int getSafeMediaVolumeIndex(int device) {
        if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE && mSafeMediaVolumeDevices.contains(
                device)) {
        if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE
                && mSafeMediaVolumeDevices.containsKey(device)) {
            return safeMediaVolumeIndex(device);
        } else {
            return -1;
@@ -557,7 +596,7 @@ public class SoundDoseHelper {

    /*package*/ boolean raiseVolumeDisplaySafeMediaVolume(int streamType, int index, int device,
            int flags) {
        if (checkSafeMediaVolume(streamType, index, device)) {
        if (!checkSafeMediaVolume(streamType, index, device)) {
            return false;
        }

@@ -566,7 +605,7 @@ public class SoundDoseHelper {
    }

    /*package*/ boolean safeDevicesContains(int device) {
        return mSafeMediaVolumeDevices.contains(device);
        return mSafeMediaVolumeDevices.containsKey(device);
    }

    /*package*/ void invalidatPendingVolumeCommand() {
@@ -612,8 +651,11 @@ public class SoundDoseHelper {
        pw.print("  mSafeMediaVolumeState=");
        pw.println(safeMediaVolumeStateToString(mSafeMediaVolumeState));
        pw.print("  mSafeMediaVolumeIndex="); pw.println(mSafeMediaVolumeIndex);
        pw.print("  mSafeUsbMediaVolumeIndex="); pw.println(mSafeUsbMediaVolumeIndex);
        pw.print("  mSafeUsbMediaVolumeDbfs="); pw.println(mSafeUsbMediaVolumeDbfs);
        for (SafeDeviceVolumeInfo vi : mSafeMediaVolumeDevices.values()) {
            pw.print("  mSafeMediaVolumeIndex["); pw.print(vi.mDeviceType);
            pw.print("]="); pw.println(vi.mSafeVolumeIndex);
        }
        pw.print("  mSafeMediaVolumeDbfs="); pw.println(mSafeMediaVolumeDbfs);
        pw.print("  mMusicActiveMs="); pw.println(mMusicActiveMs);
        pw.print("  mMcc="); pw.println(mMcc);
        pw.print("  mPendingVolumeCommand="); pw.println(mPendingVolumeCommand);
@@ -660,11 +702,12 @@ public class SoundDoseHelper {
            if (!isAbsoluteVolume) {
                // remove any possible previous attenuation
                soundDose.updateAttenuation(/* attenuationDB= */0.f, device);

                return;
            }

            if (AudioService.mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC
                    && mSafeMediaCsdDevices.contains(device)) {
                    && mSafeMediaVolumeDevices.containsKey(device)) {
                soundDose.updateAttenuation(
                        AudioSystem.getStreamVolumeDB(AudioSystem.STREAM_MUSIC,
                                (newIndex + 5) / 10,
@@ -715,7 +758,7 @@ public class SoundDoseHelper {
                mSafeMediaVolumeIndex = mContext.getResources().getInteger(
                        com.android.internal.R.integer.config_safe_media_volume_index) * 10;

                mSafeUsbMediaVolumeIndex = getSafeUsbMediaVolumeIndex();
                initSafeMediaVolumeIndex();

                boolean safeMediaVolumeEnabled =
                        SystemProperties.getBoolean("audio.safemedia.force", false)
@@ -728,7 +771,7 @@ public class SoundDoseHelper {
                // The persisted state is either "disabled" or "active": this is the state applied
                // next time we boot and cannot be "inactive"
                int persistedState;
                if (safeMediaVolumeEnabled && !safeMediaVolumeBypass && !mEnableCsd) {
                if (safeMediaVolumeEnabled && !safeMediaVolumeBypass) {
                    persistedState = SAFE_MEDIA_VOLUME_ACTIVE;
                    // The state can already be "inactive" here if the user has forced it before
                    // the 30 seconds timeout for forced configuration. In this case we don't reset
@@ -801,25 +844,32 @@ public class SoundDoseHelper {
        mAudioHandler.obtainMessage(MSG_PERSIST_MUSIC_ACTIVE_MS, mMusicActiveMs, 0).sendToTarget();
    }

    private int getSafeUsbMediaVolumeIndex() {
    private int getSafeDeviceMediaVolumeIndex(int deviceType) {
        // legacy implementation uses mSafeMediaVolumeIndex for wired HS/HP
        // instead of computing it from the volume curves
        if ((deviceType == AudioSystem.DEVICE_OUT_WIRED_HEADPHONE
                || deviceType == AudioSystem.DEVICE_OUT_WIRED_HEADSET) && !mEnableCsd) {
            return mSafeMediaVolumeIndex;
        }

        // determine UI volume index corresponding to the wanted safe gain in dBFS
        int min = MIN_STREAM_VOLUME[AudioSystem.STREAM_MUSIC];
        int max = MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC];

        mSafeUsbMediaVolumeDbfs = mContext.getResources().getInteger(
        mSafeMediaVolumeDbfs = mContext.getResources().getInteger(
                com.android.internal.R.integer.config_safe_media_volume_usb_mB) / 100.0f;

        while (Math.abs(max - min) > 1) {
            int index = (max + min) / 2;
            float gainDB = AudioSystem.getStreamVolumeDB(
                    AudioSystem.STREAM_MUSIC, index, AudioSystem.DEVICE_OUT_USB_HEADSET);
            float gainDB = AudioSystem.getStreamVolumeDB(AudioSystem.STREAM_MUSIC, index,
                    deviceType);
            if (Float.isNaN(gainDB)) {
                //keep last min in case of read error
                break;
            } else if (gainDB == mSafeUsbMediaVolumeDbfs) {
            } else if (gainDB == mSafeMediaVolumeDbfs) {
                min = index;
                break;
            } else if (gainDB < mSafeUsbMediaVolumeDbfs) {
            } else if (gainDB < mSafeMediaVolumeDbfs) {
                min = index;
            } else {
                max = index;