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

Commit 51aca3ee authored by François Gaffie's avatar François Gaffie Committed by Eric Laurent
Browse files

audioservice: fix persistency for volume per attributes



Volumes can be set by stream or attributes. Attributes based API
does neither uses a cache nor persistent storage.
So, settings are not persistent, shared among user and in case of
audioserver crash, the volume indexes are lost at lower layer.

This CL aligns the volume per attributes API on stream based API by
   -using a local cache of indexes
   -using Settings persistent storage
   -forcing refresh on audioserver crash
   -allowing mutliuser settings

It also provides dump and log to align level of service for attributes
based volume API.

Bug: 147208571
Test: -change user && dumpsys audio && dumpsys settings
    -killall audioserver && dumpsys audio && dumpsys media.audio_policy
Signed-off-by: default avatarFrançois Gaffie <francois.gaffie@renault.com>

Change-Id: I042b13f0588b2ae3f88fa9f8d72d0f0c99e5b850
Merged-In: I042b13f0588b2ae3f88fa9f8d72d0f0c99e5b850
parent 9dd718c5
Loading
Loading
Loading
Loading
+338 −15
Original line number Diff line number Diff line
@@ -124,6 +124,7 @@ import android.util.Log;
import android.util.MathUtils;
import android.util.PrintWriterPrinter;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.view.KeyEvent;
import android.view.accessibility.AccessibilityManager;
@@ -243,6 +244,7 @@ public class AudioService extends IAudioService.Stub
    // AudioHandler messages
    private static final int MSG_SET_DEVICE_VOLUME = 0;
    private static final int MSG_PERSIST_VOLUME = 1;
    private static final int MSG_PERSIST_VOLUME_GROUP = 2;
    private static final int MSG_PERSIST_RINGER_MODE = 3;
    private static final int MSG_AUDIO_SERVER_DIED = 4;
    private static final int MSG_PLAY_SOUND_EFFECT = 5;
@@ -761,6 +763,10 @@ public class AudioService extends IAudioService.Stub
        mSettingsObserver = new SettingsObserver();
        createStreamStates();

        // must be called after createStreamStates() as it uses MUSIC volume as default if no
        // persistent data
        initVolumeGroupStates();

        // mSafeUsbMediaVolumeIndex must be initialized after createStreamStates() because it
        // relies on audio policy having correct ranges for volume indexes.
        mSafeUsbMediaVolumeIndex = getSafeUsbMediaVolumeIndex();
@@ -995,6 +1001,9 @@ public class AudioService extends IAudioService.Stub
            streamState.applyAllVolumes();
        }

        // Restore audio volume groups
        restoreVolumeGroups();

        // Restore mono mode
        updateMasterMono(mContentResolver);

@@ -2112,20 +2121,20 @@ public class AudioService extends IAudioService.Stub
                                            String callingPackage) {
        enforceModifyAudioRoutingPermission();
        Preconditions.checkNotNull(attr, "attr must not be null");
        // @todo not hold the caller context, post message
        int stream = AudioProductStrategy.getLegacyStreamTypeForStrategyWithAudioAttributes(attr);
        final int device = getDeviceForStream(stream);

        int oldIndex = AudioSystem.getVolumeIndexForAttributes(attr, device);

        AudioSystem.setVolumeIndexForAttributes(attr, index, device);

        final int volumeGroup = getVolumeGroupIdForAttributes(attr);
        final AudioVolumeGroup avg = getAudioVolumeGroupById(volumeGroup);
        if (avg == null) {
        if (sVolumeGroupStates.indexOfKey(volumeGroup) < 0) {
            Log.e(TAG, ": no volume group found for attributes " + attr.toString());
            return;
        }
        for (final int groupedStream : avg.getLegacyStreamTypes()) {
        final VolumeGroupState vgs = sVolumeGroupStates.get(volumeGroup);

        sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_SET_GROUP_VOL, attr, vgs.name(),
                index/*val1*/, flags/*val2*/, callingPackage));

        vgs.setVolumeIndex(index, flags);

        // For legacy reason, propagate to all streams associated to this volume group
        for (final int groupedStream : vgs.getLegacyStreamTypes()) {
            setStreamVolume(groupedStream, index, flags, callingPackage, callingPackage,
                            Binder.getCallingUid());
        }
@@ -2147,10 +2156,12 @@ public class AudioService extends IAudioService.Stub
    public int getVolumeIndexForAttributes(@NonNull AudioAttributes attr) {
        enforceModifyAudioRoutingPermission();
        Preconditions.checkNotNull(attr, "attr must not be null");
        int stream = AudioProductStrategy.getLegacyStreamTypeForStrategyWithAudioAttributes(attr);
        final int device = getDeviceForStream(stream);

        return AudioSystem.getVolumeIndexForAttributes(attr, device);
        final int volumeGroup = getVolumeGroupIdForAttributes(attr);
        if (sVolumeGroupStates.indexOfKey(volumeGroup) < 0) {
            throw new IllegalArgumentException("No volume group for attributes " + attr);
        }
        final VolumeGroupState vgs = sVolumeGroupStates.get(volumeGroup);
        return vgs.getVolumeIndex();
    }

    /** @see AudioManager#getMaxVolumeIndexForAttributes(attr) */
@@ -3549,6 +3560,8 @@ public class AudioService extends IAudioService.Stub
                enforceSafeMediaVolume(TAG);
            }
        }

        readVolumeGroupsSettings();
    }

    /** @see AudioManager#setSpeakerphoneOn(boolean) */
@@ -4437,6 +4450,310 @@ public class AudioService extends IAudioService.Stub
    ///////////////////////////////////////////////////////////////////////////
    // Inner classes
    ///////////////////////////////////////////////////////////////////////////
    /**
     * Key is the AudioManager VolumeGroupId
     * Value is the VolumeGroupState
     */
    private static final SparseArray<VolumeGroupState> sVolumeGroupStates = new SparseArray<>();

    private void initVolumeGroupStates() {
        for (final AudioVolumeGroup avg : getAudioVolumeGroups()) {
            try {
                // if no valid attributes, this volume group is not controllable, throw exception
                ensureValidAttributes(avg);
            } catch (IllegalArgumentException e) {
                // Volume Groups without attributes are not controllable through set/get volume
                // using attributes. Do not append them.
                Log.d(TAG, "volume group " + avg.name() + " for internal policy needs");
                continue;
            }
            sVolumeGroupStates.append(avg.getId(), new VolumeGroupState(avg));
        }
        for (int i = 0; i < sVolumeGroupStates.size(); i++) {
            final VolumeGroupState vgs = sVolumeGroupStates.valueAt(i);
            vgs.applyAllVolumes();
        }
    }

    private void ensureValidAttributes(AudioVolumeGroup avg) {
        boolean hasAtLeastOneValidAudioAttributes = avg.getAudioAttributes().stream()
                .anyMatch(aa -> !aa.equals(AudioProductStrategy.sDefaultAttributes));
        if (!hasAtLeastOneValidAudioAttributes) {
            throw new IllegalArgumentException("Volume Group " + avg.name()
                    + " has no valid audio attributes");
        }
    }

    private void readVolumeGroupsSettings() {
        Log.v(TAG, "readVolumeGroupsSettings");
        for (int i = 0; i < sVolumeGroupStates.size(); i++) {
            final VolumeGroupState vgs = sVolumeGroupStates.valueAt(i);
            vgs.readSettings();
            vgs.applyAllVolumes();
        }
    }

    // Called upon crash of AudioServer
    private void restoreVolumeGroups() {
        Log.v(TAG, "restoreVolumeGroups");
        for (int i = 0; i < sVolumeGroupStates.size(); i++) {
            final VolumeGroupState vgs = sVolumeGroupStates.valueAt(i);
            vgs.applyAllVolumes();
        }
    }

    private void dumpVolumeGroups(PrintWriter pw) {
        pw.println("\nVolume Groups (device: index)");
        for (int i = 0; i < sVolumeGroupStates.size(); i++) {
            final VolumeGroupState vgs = sVolumeGroupStates.valueAt(i);
            vgs.dump(pw);
            pw.println("");
        }
    }

    // NOTE: Locking order for synchronized objects related to volume management:
    //  1     mSettingsLock
    //  2       VolumeGroupState.class
    private class VolumeGroupState {
        private final AudioVolumeGroup mAudioVolumeGroup;
        private final SparseIntArray mIndexMap = new SparseIntArray(8);
        private int mIndexMin;
        private int mIndexMax;
        private int mLegacyStreamType = AudioSystem.STREAM_DEFAULT;
        private int mPublicStreamType = AudioSystem.STREAM_MUSIC;
        private AudioAttributes mAudioAttributes = AudioProductStrategy.sDefaultAttributes;

        // No API in AudioSystem to get a device from strategy or from attributes.
        // Need a valid public stream type to use current API getDeviceForStream
        private int getDeviceForVolume() {
            return getDeviceForStream(mPublicStreamType);
        }

        private VolumeGroupState(AudioVolumeGroup avg) {
            mAudioVolumeGroup = avg;
            Log.v(TAG, "VolumeGroupState for " + avg.toString());
            for (final AudioAttributes aa : avg.getAudioAttributes()) {
                if (!aa.equals(AudioProductStrategy.sDefaultAttributes)) {
                    mAudioAttributes = aa;
                    break;
                }
            }
            final int[] streamTypes = mAudioVolumeGroup.getLegacyStreamTypes();
            if (streamTypes.length != 0) {
                // Uses already initialized MIN / MAX if a stream type is attached to group
                mLegacyStreamType = streamTypes[0];
                for (final int streamType : streamTypes) {
                    if (streamType != AudioSystem.STREAM_DEFAULT
                            && streamType < AudioSystem.getNumStreamTypes()) {
                        mPublicStreamType = streamType;
                        break;
                    }
                }
                mIndexMin = MIN_STREAM_VOLUME[mPublicStreamType];
                mIndexMax = MAX_STREAM_VOLUME[mPublicStreamType];
            } else if (!avg.getAudioAttributes().isEmpty()) {
                mIndexMin = AudioSystem.getMinVolumeIndexForAttributes(mAudioAttributes);
                mIndexMax = AudioSystem.getMaxVolumeIndexForAttributes(mAudioAttributes);
            } else {
                Log.e(TAG, "volume group: " + mAudioVolumeGroup.name()
                        + " has neither valid attributes nor valid stream types assigned");
                return;
            }
            // Load volume indexes from data base
            readSettings();
        }

        public @NonNull int[] getLegacyStreamTypes() {
            return mAudioVolumeGroup.getLegacyStreamTypes();
        }

        public String name() {
            return mAudioVolumeGroup.name();
        }

        public int getVolumeIndex() {
            return getIndex(getDeviceForVolume());
        }

        public void setVolumeIndex(int index, int flags) {
            if (mUseFixedVolume) {
                return;
            }
            setVolumeIndex(index, getDeviceForVolume(), flags);
        }

        private void setVolumeIndex(int index, int device, int flags) {
            // Set the volume index
            setVolumeIndexInt(index, device, flags);

            // Update local cache
            mIndexMap.put(device, index);

            // update data base - post a persist volume group msg
            sendMsg(mAudioHandler,
                    MSG_PERSIST_VOLUME_GROUP,
                    SENDMSG_QUEUE,
                    device,
                    0,
                    this,
                    PERSIST_DELAY);
        }

        private void setVolumeIndexInt(int index, int device, int flags) {
            // Set the volume index
            AudioSystem.setVolumeIndexForAttributes(mAudioAttributes, index, device);
        }

        public int getIndex(int device) {
            synchronized (VolumeGroupState.class) {
                int index = mIndexMap.get(device, -1);
                // there is always an entry for AudioSystem.DEVICE_OUT_DEFAULT
                return (index != -1) ? index : mIndexMap.get(AudioSystem.DEVICE_OUT_DEFAULT);
            }
        }

        public boolean hasIndexForDevice(int device) {
            synchronized (VolumeGroupState.class) {
                return (mIndexMap.get(device, -1) != -1);
            }
        }

        public int getMaxIndex() {
            return mIndexMax;
        }

        public int getMinIndex() {
            return mIndexMin;
        }

        public void applyAllVolumes() {
            synchronized (VolumeGroupState.class) {
                if (mLegacyStreamType != AudioSystem.STREAM_DEFAULT) {
                    // No-op to avoid regression with stream based volume management
                    return;
                }
                // apply device specific volumes first
                int index;
                for (int i = 0; i < mIndexMap.size(); i++) {
                    final int device = mIndexMap.keyAt(i);
                    if (device != AudioSystem.DEVICE_OUT_DEFAULT) {
                        index = mIndexMap.valueAt(i);
                        Log.v(TAG, "applyAllVolumes: restore index " + index + " for group "
                                + mAudioVolumeGroup.name() + " and device "
                                + AudioSystem.getOutputDeviceName(device));
                        setVolumeIndexInt(index, device, 0 /*flags*/);
                    }
                }
                // apply default volume last: by convention , default device volume will be used
                index = getIndex(AudioSystem.DEVICE_OUT_DEFAULT);
                Log.v(TAG, "applyAllVolumes: restore default device index " + index + " for group "
                        + mAudioVolumeGroup.name());
                setVolumeIndexInt(index, AudioSystem.DEVICE_OUT_DEFAULT, 0 /*flags*/);
            }
        }

        private void persistVolumeGroup(int device) {
            if (mUseFixedVolume) {
                return;
            }
            Log.v(TAG, "persistVolumeGroup: storing index " + getIndex(device) + " for group "
                    + mAudioVolumeGroup.name() + " and device "
                    + AudioSystem.getOutputDeviceName(device));
            boolean success = Settings.System.putIntForUser(mContentResolver,
                    getSettingNameForDevice(device),
                    getIndex(device),
                    UserHandle.USER_CURRENT);
            if (!success) {
                Log.e(TAG, "persistVolumeGroup failed for group " +  mAudioVolumeGroup.name());
            }
        }

        public void readSettings() {
            synchronized (VolumeGroupState.class) {
                // First clear previously loaded (previous user?) settings
                mIndexMap.clear();
                // force maximum volume on all streams if fixed volume property is set
                if (mUseFixedVolume) {
                    mIndexMap.put(AudioSystem.DEVICE_OUT_DEFAULT, mIndexMax);
                    return;
                }
                for (int device : AudioSystem.DEVICE_OUT_ALL_SET) {
                    // retrieve current volume for device
                    // if no volume stored for current volume group and device, use default volume
                    // if default device, continue otherwise
                    int defaultIndex = (device == AudioSystem.DEVICE_OUT_DEFAULT)
                            ? AudioSystem.DEFAULT_STREAM_VOLUME[mPublicStreamType] : -1;
                    int index;
                    String name = getSettingNameForDevice(device);
                    index = Settings.System.getIntForUser(
                            mContentResolver, name, defaultIndex, UserHandle.USER_CURRENT);
                    if (index == -1) {
                        Log.e(TAG, "readSettings: No index stored for group "
                                + mAudioVolumeGroup.name() + ", device " + name);
                        continue;
                    }
                    Log.v(TAG, "readSettings: found stored index " + getValidIndex(index)
                             + " for group " + mAudioVolumeGroup.name() + ", device: " + name);
                    mIndexMap.put(device, getValidIndex(index));
                }
            }
        }

        private int getValidIndex(int index) {
            if (index < mIndexMin) {
                return mIndexMin;
            } else if (mUseFixedVolume || index > mIndexMax) {
                return mIndexMax;
            }
            return index;
        }

        public @NonNull String getSettingNameForDevice(int device) {
            final String suffix = AudioSystem.getOutputDeviceName(device);
            if (suffix.isEmpty()) {
                return mAudioVolumeGroup.name();
            }
            return mAudioVolumeGroup.name() + "_" + AudioSystem.getOutputDeviceName(device);
        }

        private void dump(PrintWriter pw) {
            pw.println("- VOLUME GROUP " + mAudioVolumeGroup.name() + ":");
            pw.print("   Min: ");
            pw.println(mIndexMin);
            pw.print("   Max: ");
            pw.println(mIndexMax);
            pw.print("   Current: ");
            for (int i = 0; i < mIndexMap.size(); i++) {
                if (i > 0) {
                    pw.print(", ");
                }
                final int device = mIndexMap.keyAt(i);
                pw.print(Integer.toHexString(device));
                final String deviceName = device == AudioSystem.DEVICE_OUT_DEFAULT ? "default"
                        : AudioSystem.getOutputDeviceName(device);
                if (!deviceName.isEmpty()) {
                    pw.print(" (");
                    pw.print(deviceName);
                    pw.print(")");
                }
                pw.print(": ");
                pw.print(mIndexMap.valueAt(i));
            }
            pw.println();
            pw.print("   Devices: ");
            int n = 0;
            final int devices = getDeviceForVolume();
            for (int device : AudioSystem.DEVICE_OUT_ALL_SET) {
                if ((devices & device) == device) {
                    if (n++ > 0) {
                        pw.print(", ");
                    }
                    pw.print(AudioSystem.getOutputDeviceName(device));
                }
            }
        }
    }


    // NOTE: Locking order for synchronized objects related to volume or ringer mode management:
    //  1 mScoclient OR mSafeMediaVolumeState
@@ -5081,6 +5398,11 @@ public class AudioService extends IAudioService.Stub
                    persistVolume((VolumeStreamState) msg.obj, msg.arg1);
                    break;

                case MSG_PERSIST_VOLUME_GROUP:
                    final VolumeGroupState vgs = (VolumeGroupState) msg.obj;
                    vgs.persistVolumeGroup(msg.arg1);
                    break;

                case MSG_PERSIST_RINGER_MODE:
                    // note that the value persisted is the current ringer mode, not the
                    // value of ringer mode as of the time the request was made to persist
@@ -6128,6 +6450,7 @@ public class AudioService extends IAudioService.Stub
        }
        mMediaFocusControl.dump(pw);
        dumpStreamStates(pw);
        dumpVolumeGroups(pw);
        dumpRingerMode(pw);
        pw.println("\nAudio routes:");
        pw.print("  mMainType=0x"); pw.println(Integer.toHexString(
+33 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.server.audio;

import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.AudioSystem;

@@ -97,12 +98,15 @@ public class AudioServiceEvents {
        static final int VOL_ADJUST_VOL_UID = 5;
        static final int VOL_VOICE_ACTIVITY_HEARING_AID = 6;
        static final int VOL_MODE_CHANGE_HEARING_AID = 7;
        static final int VOL_SET_GROUP_VOL = 8;

        final int mOp;
        final int mStream;
        final int mVal1;
        final int mVal2;
        final String mCaller;
        final String mGroupName;
        final AudioAttributes mAudioAttributes;

        /** used for VOL_ADJUST_VOL_UID,
         *           VOL_ADJUST_SUGG_VOL,
@@ -114,6 +118,8 @@ public class AudioServiceEvents {
            mVal1 = val1;
            mVal2 = val2;
            mCaller = caller;
            mGroupName = null;
            mAudioAttributes = null;
        }

        /** used for VOL_SET_HEARING_AID_VOL*/
@@ -124,6 +130,8 @@ public class AudioServiceEvents {
            // unused
            mStream = -1;
            mCaller = null;
            mGroupName = null;
            mAudioAttributes = null;
        }

        /** used for VOL_SET_AVRCP_VOL */
@@ -134,6 +142,8 @@ public class AudioServiceEvents {
            mVal2 = 0;
            mStream = -1;
            mCaller = null;
            mGroupName = null;
            mAudioAttributes = null;
        }

        /** used for VOL_VOICE_ACTIVITY_HEARING_AID */
@@ -144,6 +154,8 @@ public class AudioServiceEvents {
            mVal2 = voiceActive ? 1 : 0;
            // unused
            mCaller = null;
            mGroupName = null;
            mAudioAttributes = null;
        }

        /** used for VOL_MODE_CHANGE_HEARING_AID */
@@ -154,6 +166,19 @@ public class AudioServiceEvents {
            mVal2 = mode;
            // unused
            mCaller = null;
            mGroupName = null;
            mAudioAttributes = null;
        }

        /** used for VOL_SET_GROUP_VOL */
        VolumeEvent(int op, AudioAttributes aa, String group, int index, int flags, String caller) {
            mOp = op;
            mStream = -1;
            mVal1 = index;
            mVal2 = flags;
            mCaller = caller;
            mGroupName = group;
            mAudioAttributes = aa;
        }

        @Override
@@ -208,6 +233,14 @@ public class AudioServiceEvents {
                            .append(") causes setting HEARING_AID volume to idx:").append(mVal1)
                            .append(" stream:").append(AudioSystem.streamToString(mStream))
                            .toString();
                case VOL_SET_GROUP_VOL:
                    return new StringBuilder("setVolumeIndexForAttributes(attr:")
                            .append(mAudioAttributes.toString())
                            .append(" group: ").append(mGroupName)
                            .append(" index:").append(mVal1)
                            .append(" flags:0x").append(Integer.toHexString(mVal2))
                            .append(") from ").append(mCaller)
                            .toString();
                default: return new StringBuilder("FIXME invalid op:").append(mOp).toString();
            }
        }