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

Commit c4b77d1f authored by Vlad Popa's avatar Vlad Popa Committed by Automerger Merge Worker
Browse files

Refactor the SADeviceState to AdiDeviceState am: 61b2a921

parents 6b2f8b3a 61b2a921
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -9925,6 +9925,13 @@ public final class Settings {
         */
        public static final String SPATIAL_AUDIO_ENABLED = "spatial_audio_enabled";
        /**
         * Internal collection of audio device inventory items
         * The device item stored are {@link com.android.server.audio.AdiDeviceState}
         * @hide
         */
        public static final String AUDIO_DEVICE_INVENTORY = "audio_device_inventory";
        /**
         * Indicates whether notification display on the lock screen is enabled.
         * <p>
+1 −0
Original line number Diff line number Diff line
@@ -700,6 +700,7 @@ public class SettingsBackupTest {
                 Settings.Secure.ASSIST_SCREENSHOT_ENABLED,
                 Settings.Secure.ASSIST_STRUCTURE_ENABLED,
                 Settings.Secure.ATTENTIVE_TIMEOUT,
                 Settings.Secure.AUDIO_DEVICE_INVENTORY, // setting not controllable by user
                 Settings.Secure.AUTOFILL_FEATURE_FIELD_CLASSIFICATION,
                 Settings.Secure.AUTOFILL_USER_DATA_MAX_CATEGORY_COUNT,
                 Settings.Secure.AUTOFILL_USER_DATA_MAX_FIELD_CLASSIFICATION_IDS_SIZE,
+204 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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 static android.media.AudioSystem.DEVICE_NONE;
import static android.media.AudioSystem.isBluetoothDevice;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceInfo;
import android.text.TextUtils;
import android.util.Log;

import java.util.Objects;

/**
 * Class representing all devices that were previously or are currently connected. Data is
 * persisted in {@link android.provider.Settings.Secure}
 */
/*package*/ final class AdiDeviceState {
    private static final String TAG = "AS.AdiDeviceState";

    private static final String SETTING_FIELD_SEPARATOR = ",";

    @AudioDeviceInfo.AudioDeviceType
    private final int mDeviceType;

    private final int mInternalDeviceType;
    @NonNull
    private final String mDeviceAddress;
    private boolean mSAEnabled;
    private boolean mHasHeadTracker = false;
    private boolean mHeadTrackerEnabled;

    /**
     * Constructor
     *
     * @param deviceType external audio device type
     * @param internalDeviceType if not set pass {@link DEVICE_NONE}, in this case the
     *                           default conversion of the external type will be used
     * @param address must be non-null for wireless devices
     * @throws NullPointerException if a null address is passed for a wireless device
     */
    AdiDeviceState(@AudioDeviceInfo.AudioDeviceType int deviceType,
                        int internalDeviceType,
                        @Nullable String address) {
        mDeviceType = deviceType;
        if (internalDeviceType != DEVICE_NONE) {
            mInternalDeviceType = internalDeviceType;
        } else {
            mInternalDeviceType = AudioDeviceInfo.convertDeviceTypeToInternalDevice(deviceType);

        }
        mDeviceAddress = isBluetoothDevice(mInternalDeviceType) ? Objects.requireNonNull(
                address) : "";
    }

    @AudioDeviceInfo.AudioDeviceType
    public int getDeviceType() {
        return mDeviceType;
    }

    public int getInternalDeviceType() {
        return mInternalDeviceType;
    }

    @NonNull
    public String getDeviceAddress() {
        return mDeviceAddress;
    }

    public void setSAEnabled(boolean sAEnabled) {
        mSAEnabled = sAEnabled;
    }

    public boolean isSAEnabled() {
        return mSAEnabled;
    }

    public void setHeadTrackerEnabled(boolean headTrackerEnabled) {
        mHeadTrackerEnabled = headTrackerEnabled;
    }

    public boolean isHeadTrackerEnabled() {
        return mHeadTrackerEnabled;
    }

    public void setHasHeadTracker(boolean hasHeadTracker) {
        mHasHeadTracker = hasHeadTracker;
    }


    public boolean hasHeadTracker() {
        return mHasHeadTracker;
    }

    @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 AdiDeviceState sads = (AdiDeviceState) obj;
        return mDeviceType == sads.mDeviceType
                && mInternalDeviceType == sads.mInternalDeviceType
                && mDeviceAddress.equals(sads.mDeviceAddress)  // NonNull
                && mSAEnabled == sads.mSAEnabled
                && mHasHeadTracker == sads.mHasHeadTracker
                && mHeadTrackerEnabled == sads.mHeadTrackerEnabled;
    }

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

    @Override
    public String toString() {
        return "type: " + mDeviceType + "internal type: " + mInternalDeviceType
                + " addr: " + mDeviceAddress + " enabled: " + mSAEnabled
                + " HT: " + mHasHeadTracker + " HTenabled: " + mHeadTrackerEnabled;
    }

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

    /**
     * Gets the max size (including separators) when persisting the elements with
     * {@link AdiDeviceState#toPersistableString()}.
     */
    public static int getPeristedMaxSize() {
        return 36;  /* (mDeviceType)2 + (mDeviceAddresss)17 + (mInternalDeviceType)9 + (mSAEnabled)1
                           + (mHasHeadTracker)1 + (mHasHeadTrackerEnabled)1
                           + (SETTINGS_FIELD_SEPARATOR)5 */
    }

    @Nullable
    public static AdiDeviceState fromPersistedString(@Nullable String persistedString) {
        if (persistedString == null) {
            return null;
        }
        if (persistedString.isEmpty()) {
            return null;
        }
        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
            return null;
        }
        try {
            final int deviceType = Integer.parseInt(fields[0]);
            int internalDeviceType = -1;
            if (fields.length == 6) {
                internalDeviceType = Integer.parseInt(fields[5]);
            }
            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);
            return deviceState;
        } catch (NumberFormatException e) {
            Log.e(TAG, "unable to parse setting for AdiDeviceState: " + persistedString, e);
            return null;
        }
    }

    public AudioDeviceAttributes getAudioDeviceAttributes() {
        return new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT,
                mDeviceType, mDeviceAddress);
    }

}
+102 −2
Original line number Diff line number Diff line
@@ -50,6 +50,7 @@ import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import android.util.PrintWriterPrinter;
@@ -69,8 +70,11 @@ import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;


/** @hide */
/*package*/ final class AudioDeviceBroker {
/**
 * @hide
 * (non final for mocking/spying)
 */
public class AudioDeviceBroker {

    private static final String TAG = "AS.AudioDeviceBroker";

@@ -1850,6 +1854,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
                    final BluetoothDevice btDevice = (BluetoothDevice) msg.obj;
                    BtHelper.onNotifyPreferredAudioProfileApplied(btDevice);
                } break;
                case MSG_PERSIST_AUDIO_DEVICE_SETTINGS:
                    onPersistAudioDeviceSettings();
                    break;
                default:
                    Log.wtf(TAG, "Invalid message " + msg.what);
            }
@@ -1927,6 +1934,8 @@ import java.util.concurrent.atomic.AtomicBoolean;

    private static final int MSG_L_NOTIFY_PREFERRED_AUDIOPROFILE_APPLIED = 52;

    private static final int MSG_PERSIST_AUDIO_DEVICE_SETTINGS = 54;

    private static boolean isMessageHandledUnderWakelock(int msgId) {
        switch(msgId) {
            case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE:
@@ -2354,4 +2363,95 @@ import java.util.concurrent.atomic.AtomicBoolean;
                info.getId(),
                null /*mixerAttributes*/);
    }

    /**
     * post a message to persist the 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 persistAudioDeviceSettings() {
        sendMsg(MSG_PERSIST_AUDIO_DEVICE_SETTINGS, SENDMSG_REPLACE, /*delay*/ 1000);
    }

    void onPersistAudioDeviceSettings() {
        final String deviceSettings = mDeviceInventory.getDeviceSettings();
        Log.v(TAG, "saving audio device settings: " + 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);
        }
    }

    void onReadAudioDeviceSettings() {
        final SettingsAdapter settingsAdapter = mAudioService.getSettings();
        final ContentResolver contentResolver = mAudioService.getContentResolver();
        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"
                    + 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
            // device settings when calling {@link #setDeviceSettings()}
            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");
            } 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 "
                            + 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 "
                            + Settings.Secure.AUDIO_DEVICE_INVENTORY);
                }
            }
        }

        if (settings != null && !settings.equals("")) {
            setDeviceSettings(settings);
        }
    }

    void setDeviceSettings(String settings) {
        mDeviceInventory.setDeviceSettings(settings);
    }

    /** Test only method. */
    String getDeviceSettings() {
        return mDeviceInventory.getDeviceSettings();
    }

    List<AdiDeviceState> getImmutableDeviceInventory() {
        return mDeviceInventory.getImmutableDeviceInventory();
    }

    void addDeviceStateToInventory(AdiDeviceState deviceState) {
        mDeviceInventory.addDeviceStateToInventory(deviceState);
    }

    AdiDeviceState findDeviceStateForAudioDeviceAttributes(AudioDeviceAttributes ada,
            int canonicalType) {
        return mDeviceInventory.findDeviceStateForAudioDeviceAttributes(ada, canonicalType);
    }

    //------------------------------------------------
    // for testing purposes only
    void clearDeviceInventory() {
        mDeviceInventory.clearDeviceInventory();
    }
}
+88 −4
Original line number Diff line number Diff line
@@ -15,6 +15,8 @@
 */
package com.android.server.audio;

import static android.media.AudioSystem.isBluetoothDevice;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.bluetooth.BluetoothAdapter;
@@ -78,12 +80,54 @@ public class AudioDeviceInventory {

    private static final String TAG = "AS.AudioDeviceInventory";

    private static final String SETTING_DEVICE_SEPARATOR_CHAR = "|";
    private static final String SETTING_DEVICE_SEPARATOR = "\\|";

    // lock to synchronize all access to mConnectedDevices and mApmConnectedDevices
    private final Object mDevicesLock = new Object();

    //Audio Analytics ids.
    private static final String mMetricsId = "audio.device.";

    private final Object mDeviceInventoryLock = new Object();
    @GuardedBy("mDeviceInventoryLock")
    private final ArrayList<AdiDeviceState> mDeviceInventory = new ArrayList<>(0);


    List<AdiDeviceState> getImmutableDeviceInventory() {
        synchronized (mDeviceInventoryLock) {
            return List.copyOf(mDeviceInventory);
        }
    }

    void addDeviceStateToInventory(AdiDeviceState deviceState) {
        synchronized (mDeviceInventoryLock) {
            mDeviceInventory.add(deviceState);
        }
    }

    AdiDeviceState findDeviceStateForAudioDeviceAttributes(AudioDeviceAttributes ada,
            int canonicalDeviceType) {
        final boolean isWireless = isBluetoothDevice(ada.getInternalType());

        synchronized (mDeviceInventoryLock) {
            for (AdiDeviceState deviceSetting : mDeviceInventory) {
                if (deviceSetting.getDeviceType() == canonicalDeviceType
                        && (!isWireless || ada.getAddress().equals(
                        deviceSetting.getDeviceAddress()))) {
                    return deviceSetting;
                }
            }
        }
        return null;
    }

    void clearDeviceInventory() {
        synchronized (mDeviceInventoryLock) {
            mDeviceInventory.clear();
        }
    }

    // List of connected devices
    // Key for map created from DeviceInfo.makeDeviceListKey()
    @GuardedBy("mDevicesLock")
@@ -341,6 +385,12 @@ public class AudioDeviceInventory {
        mAppliedPresetRolesInt.forEach((key, devices) -> {
            pw.println("  " + prefix + "preset: " + key.first
                    +  " role:" + key.second + " devices:" + devices); });
        pw.println("\ndevices:\n");
        synchronized (mDeviceInventoryLock) {
            for (AdiDeviceState device : mDeviceInventory) {
                pw.println("\t" + device + "\n");
            }
        }
    }

    //------------------------------------------------------------
@@ -1198,7 +1248,7 @@ public class AudioDeviceInventory {

                    AudioDeviceInfo device = Stream.of(connectedDevices)
                            .filter(d -> d.getInternalType() == ada.getInternalType())
                            .filter(d -> (!AudioSystem.isBluetoothDevice(d.getInternalType())
                            .filter(d -> (!isBluetoothDevice(d.getInternalType())
                                            || (d.getAddress().equals(ada.getAddress()))))
                            .findFirst()
                            .orElse(null);
@@ -1619,7 +1669,7 @@ public class AudioDeviceInventory {
        }

        for (DeviceInfo di : mConnectedDevices.values()) {
            if (!AudioSystem.isBluetoothDevice(di.mDeviceType)) {
            if (!isBluetoothDevice(di.mDeviceType)) {
                continue;
            }
            AudioDeviceAttributes ada =
@@ -1733,7 +1783,7 @@ public class AudioDeviceInventory {
        }
        HashSet<String> processedAddresses = new HashSet<>(0);
        for (DeviceInfo di : mConnectedDevices.values()) {
            if (!AudioSystem.isBluetoothDevice(di.mDeviceType)
            if (!isBluetoothDevice(di.mDeviceType)
                    || processedAddresses.contains(di.mDeviceAddress)) {
                continue;
            }
@@ -1743,7 +1793,7 @@ public class AudioDeviceInventory {
                        + di.mDeviceAddress + ", preferredProfiles: " + preferredProfiles);
            }
            for (DeviceInfo di2 : mConnectedDevices.values()) {
                if (!AudioSystem.isBluetoothDevice(di2.mDeviceType)
                if (!isBluetoothDevice(di2.mDeviceType)
                        || !di.mDeviceAddress.equals(di2.mDeviceAddress)) {
                    continue;
                }
@@ -2359,6 +2409,40 @@ public class AudioDeviceInventory {
        }
    }

    /*package*/ String getDeviceSettings() {
        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);
                }
            }
        }
        return settingsBuilder.toString();
    }

    /*package*/ void setDeviceSettings(String settings) {
        clearDeviceInventory();
        String[] devSettings = TextUtils.split(Objects.requireNonNull(settings),
                SETTING_DEVICE_SEPARATOR);
        // small list, not worth overhead of Arrays.stream(devSettings)
        for (String setting : devSettings) {
            AdiDeviceState devState = AdiDeviceState.fromPersistedString(setting);
            // 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);
            }
        }
    }

    //----------------------------------------------------------
    // For tests only

Loading