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

Commit 7f75f8a3 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Automerger Merge Worker
Browse files

Merge "AudioService: persist/restore audio device spatial audio settings" into...

Merge "AudioService: persist/restore audio device spatial audio settings" into tm-dev am: 11782ee9

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/18385019



Change-Id: I239f8db14a769d10caa05e97f9be2765f4fbcf3f
Signed-off-by: default avatarAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
parents dd7d5fcd 11782ee9
Loading
Loading
Loading
Loading
+45 −4
Original line number Original line Diff line number Diff line
@@ -341,8 +341,7 @@ public class AudioService extends IAudioService.Stub
    private static final int MSG_DISPATCH_AUDIO_MODE = 40;
    private static final int MSG_DISPATCH_AUDIO_MODE = 40;
    private static final int MSG_ROUTING_UPDATED = 41;
    private static final int MSG_ROUTING_UPDATED = 41;
    private static final int MSG_INIT_HEADTRACKING_SENSORS = 42;
    private static final int MSG_INIT_HEADTRACKING_SENSORS = 42;
    // commented out for now, will be reused for other SA persisting
    private static final int MSG_PERSIST_SPATIAL_AUDIO_DEVICE_SETTINGS = 43;
    //private static final int MSG_PERSIST_SPATIAL_AUDIO_ENABLED = 43;
    private static final int MSG_ADD_ASSISTANT_SERVICE_UID = 44;
    private static final int MSG_ADD_ASSISTANT_SERVICE_UID = 44;
    private static final int MSG_REMOVE_ASSISTANT_SERVICE_UID = 45;
    private static final int MSG_REMOVE_ASSISTANT_SERVICE_UID = 45;
    private static final int MSG_UPDATE_ACTIVE_ASSISTANT_SERVICE_UID = 46;
    private static final int MSG_UPDATE_ACTIVE_ASSISTANT_SERVICE_UID = 46;
@@ -8122,8 +8121,7 @@ public class AudioService extends IAudioService.Stub
                    break;
                    break;


                case MSG_INIT_SPATIALIZER:
                case MSG_INIT_SPATIALIZER:
                    mSpatializerHelper.init(/*effectExpected*/ mHasSpatializerEffect);
                    onInitSpatializer();
                    mSpatializerHelper.setFeatureEnabled(mHasSpatializerEffect);
                    mAudioEventWakeLock.release();
                    mAudioEventWakeLock.release();
                    break;
                    break;


@@ -8131,6 +8129,10 @@ public class AudioService extends IAudioService.Stub
                    mSpatializerHelper.onInitSensors();
                    mSpatializerHelper.onInitSensors();
                    break;
                    break;


                case MSG_PERSIST_SPATIAL_AUDIO_DEVICE_SETTINGS:
                    onPersistSpatialAudioDeviceSettings();
                    break;

                case MSG_CHECK_MUSIC_ACTIVE:
                case MSG_CHECK_MUSIC_ACTIVE:
                    onCheckMusicActive((String) msg.obj);
                    onCheckMusicActive((String) msg.obj);
                    break;
                    break;
@@ -9099,6 +9101,45 @@ public class AudioService extends IAudioService.Stub
                /*arg1*/ 0, /*arg2*/ 0, TAG, /*delay*/ 0);
                /*arg1*/ 0, /*arg2*/ 0, TAG, /*delay*/ 0);
    }
    }


    void onInitSpatializer() {
        final String settings = mSettings.getSecureStringForUser(mContentResolver,
                Settings.Secure.SPATIAL_AUDIO_ENABLED, UserHandle.USER_CURRENT);
        if (settings == null) {
            Log.e(TAG, "error reading spatial audio device settings");
        } else {
            Log.v(TAG, "restoring spatial audio device settings: " + settings);
            mSpatializerHelper.setSADeviceSettings(settings);
        }
        mSpatializerHelper.init(/*effectExpected*/ mHasSpatializerEffect);
        mSpatializerHelper.setFeatureEnabled(mHasSpatializerEffect);
    }

    /**
     * post a message to persist the spatial audio device settings.
     * Message is delayed by 1s on purpose in case of successive changes in quick succession (at
     * init time for instance)
     * Note this method is made public to work around a Mockito bug where it needs to be public
     * in order to be mocked by a test a the same package
     * (see https://code.google.com/archive/p/mockito/issues/127)
     */
    public void persistSpatialAudioDeviceSettings() {
        sendMsg(mAudioHandler,
                MSG_PERSIST_SPATIAL_AUDIO_DEVICE_SETTINGS,
                SENDMSG_REPLACE, /*arg1*/ 0, /*arg2*/ 0, TAG,
                /*delay*/ 1000);
    }

    void onPersistSpatialAudioDeviceSettings() {
        final String settings = mSpatializerHelper.getSADeviceSettings();
        Log.v(TAG, "saving spatial audio device settings: " + settings);
        boolean res = mSettings.putSecureStringForUser(mContentResolver,
                Settings.Secure.SPATIAL_AUDIO_ENABLED,
                settings, UserHandle.USER_CURRENT);
        if (!res) {
            Log.e(TAG, "error saving spatial audio device settings: " + settings);
        }
    }

    //==========================================================================================
    //==========================================================================================
    private boolean readCameraSoundForced() {
    private boolean readCameraSoundForced() {
        return SystemProperties.getBoolean("audio.camerasound.force", false) ||
        return SystemProperties.getBoolean("audio.camerasound.force", false) ||
+111 −10
Original line number Original line Diff line number Diff line
@@ -40,6 +40,7 @@ import android.media.Spatializer;
import android.media.SpatializerHeadTrackingMode;
import android.media.SpatializerHeadTrackingMode;
import android.os.RemoteCallbackList;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.RemoteException;
import android.text.TextUtils;
import android.util.Log;
import android.util.Log;
import android.util.Pair;
import android.util.Pair;
import android.util.SparseIntArray;
import android.util.SparseIntArray;
@@ -48,6 +49,7 @@ import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.ArrayList;
import java.util.List;
import java.util.List;
import java.util.Locale;
import java.util.Locale;
import java.util.Objects;
import java.util.UUID;
import java.util.UUID;


/**
/**
@@ -275,18 +277,17 @@ public class SpatializerHelper {
            // for both transaural / binaural, we are not forcing enablement as the init() method
            // for both transaural / binaural, we are not forcing enablement as the init() method
            // could have been called another time after boot in case of audioserver restart
            // could have been called another time after boot in case of audioserver restart
            if (mTransauralSupported) {
            if (mTransauralSupported) {
                // TODO deal with persisted values
                // not force-enabling as this device might already be in the device list
                addCompatibleAudioDevice(
                addCompatibleAudioDevice(
                        new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, ""),
                        new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, ""),
                                false /*forceEnable*/);
                                false /*forceEnable*/);
            }
            }
            if (mBinauralSupported) {
            if (mBinauralSupported) {
                // TODO deal with persisted values
                // not force-enabling as this device might already be in the device list
                addCompatibleAudioDevice(
                addCompatibleAudioDevice(
                        new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE, ""),
                        new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE, ""),
                                false /*forceEnable*/);
                                false /*forceEnable*/);
            }
            }
            // TODO read persisted states
        } catch (RemoteException e) {
        } catch (RemoteException e) {
            resetCapabilities();
            resetCapabilities();
        } finally {
        } finally {
@@ -533,11 +534,12 @@ public class SpatializerHelper {
        }
        }
        if (!isInList) {
        if (!isInList) {
            final SADeviceState dev = new SADeviceState(deviceType,
            final SADeviceState dev = new SADeviceState(deviceType,
                    wireless ? ada.getAddress() : null);
                    wireless ? ada.getAddress() : "");
            dev.mEnabled = true;
            dev.mEnabled = true;
            mSADevices.add(dev);
            mSADevices.add(dev);
        }
        }
        onRoutingUpdated();
        onRoutingUpdated();
        mAudioService.persistSpatialAudioDeviceSettings();
    }
    }


    synchronized void removeCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) {
    synchronized void removeCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) {
@@ -553,6 +555,7 @@ public class SpatializerHelper {
            }
            }
        }
        }
        onRoutingUpdated();
        onRoutingUpdated();
        mAudioService.persistSpatialAudioDeviceSettings();
    }
    }


    /**
    /**
@@ -625,7 +628,7 @@ public class SpatializerHelper {
        }
        }
        if (!knownDevice) {
        if (!knownDevice) {
            mSADevices.add(new SADeviceState(ada.getType(), ada.getAddress()));
            mSADevices.add(new SADeviceState(ada.getType(), ada.getAddress()));
            //### TODO persist list
            mAudioService.persistSpatialAudioDeviceSettings();
        }
        }
    }
    }


@@ -1059,6 +1062,7 @@ public class SpatializerHelper {
                }
                }
                Log.i(TAG, "setHeadTrackerEnabled enabled:" + enabled + " device:" + ada);
                Log.i(TAG, "setHeadTrackerEnabled enabled:" + enabled + " device:" + ada);
                deviceState.mHeadTrackerEnabled = enabled;
                deviceState.mHeadTrackerEnabled = enabled;
                mAudioService.persistSpatialAudioDeviceSettings();
                break;
                break;
            }
            }
        }
        }
@@ -1097,7 +1101,10 @@ public class SpatializerHelper {
            if (deviceType == deviceState.mDeviceType
            if (deviceType == deviceState.mDeviceType
                    && (wireless && ada.getAddress().equals(deviceState.mDeviceAddress))
                    && (wireless && ada.getAddress().equals(deviceState.mDeviceAddress))
                    || !wireless) {
                    || !wireless) {
                if (!deviceState.mHasHeadTracker) {
                    deviceState.mHasHeadTracker = true;
                    deviceState.mHasHeadTracker = true;
                    mAudioService.persistSpatialAudioDeviceSettings();
                }
                return deviceState.mHeadTrackerEnabled;
                return deviceState.mHeadTrackerEnabled;
            }
            }
        }
        }
@@ -1457,16 +1464,45 @@ public class SpatializerHelper {
        }
        }
    }
    }


    private static final class SADeviceState {
    /*package*/ static final class SADeviceState {
        final @AudioDeviceInfo.AudioDeviceType int mDeviceType;
        final @AudioDeviceInfo.AudioDeviceType int mDeviceType;
        final @Nullable String mDeviceAddress; // non-null for wireless devices
        final @NonNull String mDeviceAddress;
        boolean mEnabled = true;               // by default, SA is enabled on any device
        boolean mEnabled = true;               // by default, SA is enabled on any device
        boolean mHasHeadTracker = false;
        boolean mHasHeadTracker = false;
        boolean mHeadTrackerEnabled = true;    // by default, if head tracker is present, use it
        boolean mHeadTrackerEnabled = true;    // by default, if head tracker is present, use it
        static final String SETTING_FIELD_SEPARATOR = ",";
        static final String SETTING_DEVICE_SEPARATOR_CHAR = "|";
        static final String SETTING_DEVICE_SEPARATOR = "\\|";


        SADeviceState(@AudioDeviceInfo.AudioDeviceType int deviceType, @Nullable String address) {
        SADeviceState(@AudioDeviceInfo.AudioDeviceType int deviceType, @NonNull String address) {
            mDeviceType = deviceType;
            mDeviceType = deviceType;
            mDeviceAddress = address;
            mDeviceAddress = Objects.requireNonNull(address);
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj == null) {
                return false;
            }
            // type check and cast
            if (getClass() != obj.getClass()) {
                return false;
            }
            final SADeviceState sads = (SADeviceState) obj;
            return mDeviceType == sads.mDeviceType
                    && mDeviceAddress.equals(sads.mDeviceAddress)
                    && mEnabled == sads.mEnabled
                    && mHasHeadTracker == sads.mHasHeadTracker
                    && mHeadTrackerEnabled == sads.mHeadTrackerEnabled;
        }

        @Override
        public int hashCode() {
            return Objects.hash(mDeviceType, mDeviceAddress, mEnabled, mHasHeadTracker,
                    mHeadTrackerEnabled);
        }
        }


        @Override
        @Override
@@ -1474,6 +1510,64 @@ public class SpatializerHelper {
            return "type:" + mDeviceType + " addr:" + mDeviceAddress + " enabled:" + mEnabled
            return "type:" + mDeviceType + " addr:" + mDeviceAddress + " enabled:" + mEnabled
                    + " HT:" + mHasHeadTracker + " HTenabled:" + mHeadTrackerEnabled;
                    + " HT:" + mHasHeadTracker + " HTenabled:" + mHeadTrackerEnabled;
        }
        }

        String toPersistableString() {
            return (new StringBuilder().append(mDeviceType)
                    .append(SETTING_FIELD_SEPARATOR).append(mDeviceAddress)
                    .append(SETTING_FIELD_SEPARATOR).append(mEnabled ? "1" : "0")
                    .append(SETTING_FIELD_SEPARATOR).append(mHasHeadTracker ? "1" : "0")
                    .append(SETTING_FIELD_SEPARATOR).append(mHeadTrackerEnabled ? "1" : "0")
                    .toString());
        }

        static @Nullable SADeviceState fromPersistedString(@Nullable String persistedString) {
            if (persistedString == null) {
                return null;
            }
            if (persistedString.isEmpty()) {
                return null;
            }
            String[] fields = TextUtils.split(persistedString, SETTING_FIELD_SEPARATOR);
            if (fields.length != 5) {
                // expecting all fields, fewer may mean corruption, ignore those settings
                return null;
            }
            try {
                final int deviceType = Integer.parseInt(fields[0]);
                final SADeviceState deviceState = new SADeviceState(deviceType, fields[1]);
                deviceState.mEnabled = Integer.parseInt(fields[2]) == 1;
                deviceState.mHasHeadTracker = Integer.parseInt(fields[3]) == 1;
                deviceState.mHeadTrackerEnabled = Integer.parseInt(fields[4]) == 1;
                return deviceState;
            } catch (NumberFormatException e) {
                Log.e(TAG, "unable to parse setting for SADeviceState: " + persistedString, e);
                return null;
            }
        }
    }

    /*package*/ synchronized String getSADeviceSettings() {
        // expected max size of each String for each SADeviceState is 25 (accounting for separator)
        final StringBuilder settingsBuilder = new StringBuilder(mSADevices.size() * 25);
        for (int i = 0; i < mSADevices.size(); i++) {
            settingsBuilder.append(mSADevices.get(i).toPersistableString());
            if (i != mSADevices.size() - 1) {
                settingsBuilder.append(SADeviceState.SETTING_DEVICE_SEPARATOR_CHAR);
            }
        }
        return settingsBuilder.toString();
    }

    /*package*/ synchronized void setSADeviceSettings(@NonNull String persistedSettings) {
        String[] devSettings = TextUtils.split(Objects.requireNonNull(persistedSettings),
                SADeviceState.SETTING_DEVICE_SEPARATOR);
        // small list, not worth overhead of Arrays.stream(devSettings)
        for (String setting : devSettings) {
            SADeviceState devState = SADeviceState.fromPersistedString(setting);
            if (devState != null) {
                mSADevices.add(devState);
            }
        }
    }
    }


    private static String spatStateString(int state) {
    private static String spatStateString(int state) {
@@ -1557,4 +1651,11 @@ public class SpatializerHelper {
        AudioService.sSpatialLogger.loglog(msg, AudioEventLogger.Event.ALOGE, TAG);
        AudioService.sSpatialLogger.loglog(msg, AudioEventLogger.Event.ALOGE, TAG);
        return msg;
        return msg;
    }
    }

    //------------------------------------------------
    // for testing purposes only

    /*package*/ void clearSADevices() {
        mSADevices.clear();
    }
}
}
+137 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.android.server.audio;

import com.android.server.audio.SpatializerHelper.SADeviceState;

import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;

import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceInfo;
import android.media.AudioSystem;
import android.util.Log;

import androidx.test.filters.MediumTest;
import androidx.test.runner.AndroidJUnit4;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Spy;

import java.util.List;

@MediumTest
@RunWith(AndroidJUnit4.class)
public class SpatializerHelperTest {

    private static final String TAG = "SpatializerHelperTest";

    // the actual class under test
    private SpatializerHelper mSpatHelper;

    @Mock private AudioService mMockAudioService;
    @Spy private AudioSystemAdapter mSpyAudioSystem;

    @Before
    public void setUp() throws Exception {
        mMockAudioService = mock(AudioService.class);
        mSpyAudioSystem = spy(new NoOpAudioSystemAdapter());

        mSpatHelper = new SpatializerHelper(mMockAudioService, mSpyAudioSystem);
    }

    @Test
    public void testSADeviceStateNullAddressCtor() throws Exception {
        try {
            SADeviceState devState = new SADeviceState(
                    AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, null);
            Assert.fail();
        } catch (NullPointerException e) { }
    }

    @Test
    public void testSADeviceStateStringSerialization() throws Exception {
        Log.i(TAG, "starting testSADeviceStateStringSerialization");
        final SADeviceState devState = new SADeviceState(
                AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, "bla");
        devState.mHasHeadTracker = false;
        devState.mHeadTrackerEnabled = false;
        devState.mEnabled = true;
        final String persistString = devState.toPersistableString();
        final SADeviceState result = SADeviceState.fromPersistedString(persistString);
        Log.i(TAG, "original:" + devState);
        Log.i(TAG, "result  :" + result);
        Assert.assertEquals(devState, result);
    }

    @Test
    public void testSADeviceSettings() throws Exception {
        Log.i(TAG, "starting testSADeviceSettings");
        final AudioDeviceAttributes dev1 =
                new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, "");
        final AudioDeviceAttributes dev2 =
                new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, "C3:P0:beep");
        final AudioDeviceAttributes dev3 =
                new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, "R2:D2:bloop");

        doNothing().when(mMockAudioService).persistSpatialAudioDeviceSettings();

        // test with single device
        mSpatHelper.addCompatibleAudioDevice(dev1);
        checkAddSettings();

        // test with 2+ devices so separator character is used in list
        mSpatHelper.addCompatibleAudioDevice(dev2);
        Assert.assertTrue(mSpatHelper.isAvailableForDevice(dev2));
        checkAddSettings();
        Assert.assertTrue(mSpatHelper.isAvailableForDevice(dev2));
        mSpatHelper.addCompatibleAudioDevice(dev3);
        checkAddSettings();

        // test adding a device twice in the list
        mSpatHelper.addCompatibleAudioDevice(dev1);
        checkAddSettings();

        // test removing a device
        mSpatHelper.removeCompatibleAudioDevice(dev2);
        // spatializer could still be run for dev2 (is available) but spatial audio
        // is disabled for dev2 by removeCompatibleAudioDevice
        Assert.assertTrue(mSpatHelper.isAvailableForDevice(dev2));
        List<AudioDeviceAttributes> compatDevices = mSpatHelper.getCompatibleAudioDevices();
        Assert.assertFalse(compatDevices.stream().anyMatch(dev -> dev.equalTypeAddress(dev2)));
        checkAddSettings();
    }

    /**
     * Gets the string representing the current configuration of the devices, then clears it
     * and restores the configuration. Verify the new string from the restored settings matches
     * the original one.
     */
    private void checkAddSettings() throws Exception {
        String settings = mSpatHelper.getSADeviceSettings();
        Log.i(TAG, "device settings: " + settings);
        mSpatHelper.clearSADevices();
        mSpatHelper.setSADeviceSettings(settings);
        String settingsRestored = mSpatHelper.getSADeviceSettings();
        Log.i(TAG, "device settingsRestored: " + settingsRestored);
        Assert.assertEquals(settings, settingsRestored);
    }
}