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

Commit 2b6df364 authored by Eric Laurent's avatar Eric Laurent Committed by Gerrit Code Review
Browse files

Merge "audioservice: fix persistency for volume per attributes"

parents 090bad77 51aca3ee
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();
            }
        }