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

Commit b5d7e0cd authored by Haijie Hong's avatar Haijie Hong Committed by Android (Google) Code Review
Browse files

Merge "Use SliderPreference in audio sharing dashboard" into main

parents 889381c0 283505bd
Loading
Loading
Loading
Loading
+46 −16
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import com.android.settings.bluetooth.BluetoothDevicePreference;
import com.android.settings.bluetooth.BluetoothDeviceUpdater;
import com.android.settings.bluetooth.Utils;
import com.android.settings.connecteddevice.DevicePreferenceCallback;
import com.android.settings.flags.Flags;
import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
@@ -82,6 +83,15 @@ public class AudioSharingDeviceVolumeControlUpdater extends BluetoothDeviceUpdat
        if (cachedDevice == null) return;
        final BluetoothDevice device = cachedDevice.getDevice();
        if (!mPreferenceMap.containsKey(device)) {
            if (Flags.enableBluetoothSettingsExpressiveDesign()) {
                AudioSharingDeviceVolumeSliderPreference vPreference =
                        new AudioSharingDeviceVolumeSliderPreference(mPrefContext, cachedDevice);
                vPreference.initialize();
                vPreference.setKey(getPreferenceKeyPrefix() + cachedDevice.hashCode());
                vPreference.setIcon(com.android.settingslib.R.drawable.ic_bt_untethered_earbuds);
                mPreferenceMap.put(device, vPreference);
                mDevicePreferenceCallback.onDeviceAdded(vPreference);
            } else {
                AudioSharingDeviceVolumePreference vPreference =
                        new AudioSharingDeviceVolumePreference(mPrefContext, cachedDevice);
                vPreference.initialize();
@@ -91,12 +101,20 @@ public class AudioSharingDeviceVolumeControlUpdater extends BluetoothDeviceUpdat
                mDevicePreferenceCallback.onDeviceAdded(vPreference);
            }
        }
    }

    @Override
    public void refreshPreference() {
        mPreferenceMap.forEach((key, preference) -> {
        mPreferenceMap.forEach(
                (key, preference) -> {
                    if (isDeviceOfMapInCachedDevicesList(key)) {
                ((AudioSharingDeviceVolumePreference) preference).onPreferenceAttributesChanged();
                        if (Flags.enableBluetoothSettingsExpressiveDesign()) {
                            ((AudioSharingDeviceVolumeSliderPreference) preference)
                                    .onPreferenceAttributesChanged();
                        } else {
                            ((AudioSharingDeviceVolumePreference) preference)
                                    .onPreferenceAttributesChanged();
                        }
                    } else {
                        // Remove staled preference.
                        Log.d(TAG, "removePreference key: " + key.getAnonymizedAddress());
@@ -119,6 +137,18 @@ public class AudioSharingDeviceVolumeControlUpdater extends BluetoothDeviceUpdat
                    Log.w(TAG, "Inconsistent key and preference when removePreference");
                }
                mPreferenceMap.remove(device);
            } else if (mPreferenceMap.get(device)
                    instanceof AudioSharingDeviceVolumeSliderPreference pref) {
                BluetoothDevice prefDevice = pref.getCachedDevice().getDevice();
                // For CSIP device, when it {@link CachedBluetoothDevice}#switchMemberDeviceContent,
                // it will change its mDevice and lead to the hashcode change for this preference.
                // This will cause unintended remove preference, see b/394765052
                if (device.equals(prefDevice) || !mPreferenceMap.containsKey(prefDevice)) {
                    mDevicePreferenceCallback.onDeviceRemoved(pref);
                } else {
                    Log.w(TAG, "Inconsistent key and preference when removePreference");
                }
                mPreferenceMap.remove(device);
            } else {
                mDevicePreferenceCallback.onDeviceRemoved(mPreferenceMap.get(device));
                mPreferenceMap.remove(device);
+29 −13
Original line number Diff line number Diff line
@@ -77,7 +77,7 @@ public class AudioSharingDeviceVolumeGroupController extends AudioSharingBasePre
    private final Executor mExecutor;
    private final ContentObserver mSettingsObserver;
    @Nullable private PreferenceGroup mPreferenceGroup;
    private CopyOnWriteArraySet<AudioSharingDeviceVolumePreference> mVolumePreferences =
    private CopyOnWriteArraySet<Preference> mVolumePreferences =
            new CopyOnWriteArraySet<>();
    private ConcurrentHashMap<Integer, Integer> mValueMap = new ConcurrentHashMap<>();
    private AtomicBoolean mCallbacksRegistered = new AtomicBoolean(false);
@@ -96,9 +96,9 @@ public class AudioSharingDeviceVolumeGroupController extends AudioSharingBasePre
                    if (cachedDevice == null) return;
                    int groupId = BluetoothUtils.getGroupId(cachedDevice);
                    mValueMap.put(groupId, volume);
                    for (AudioSharingDeviceVolumePreference preference : mVolumePreferences) {
                        if (preference.getCachedDevice() != null
                                && BluetoothUtils.getGroupId(preference.getCachedDevice())
                    for (Preference preference : mVolumePreferences) {
                        if (getCachedDevice(preference) != null
                                && BluetoothUtils.getGroupId(getCachedDevice(preference))
                                        == groupId) {
                            // If the callback return invalid volume, try to
                            // get the volume from AudioManager.STREAM_MUSIC
@@ -110,7 +110,7 @@ public class AudioSharingDeviceVolumeGroupController extends AudioSharingBasePre
                                            + " for "
                                            + device.getAnonymizedAddress());
                            AudioSharingUtils.postOnMainThread(mContext,
                                    () -> preference.setProgress(finalVolume));
                                    () -> setValue(preference, finalVolume));
                            break;
                        }
                    }
@@ -184,8 +184,8 @@ public class AudioSharingDeviceVolumeGroupController extends AudioSharingBasePre
        public void onBroadcastToUnicastFallbackGroupChanged(int groupId) {
            if (!Flags.adoptPrimaryGroupManagementApiV2()) return;
            Log.d(TAG, "onBroadcastToUnicastFallbackGroupChanged, group id = " + groupId);
            for (AudioSharingDeviceVolumePreference preference : mVolumePreferences) {
                int order = getPreferenceOrderForDevice(preference.getCachedDevice());
            for (Preference preference : mVolumePreferences) {
                int order = getPreferenceOrderForDevice(getCachedDevice(preference));
                AudioSharingUtils.postOnMainThread(mContext, () -> preference.setOrder(order));
            }
        }
@@ -229,8 +229,8 @@ public class AudioSharingDeviceVolumeGroupController extends AudioSharingBasePre
            if (Flags.adoptPrimaryGroupManagementApiV2()) return;
            // TODO: remove content observer once switch to API
            Log.d(TAG, "onChange, fallback device group id has been changed");
            for (AudioSharingDeviceVolumePreference preference : mVolumePreferences) {
                int order = getPreferenceOrderForDevice(preference.getCachedDevice());
            for (Preference preference : mVolumePreferences) {
                int order = getPreferenceOrderForDevice(getCachedDevice(preference));
                Log.d(TAG, "onChange: set order to " + order + " for " + preference);
                AudioSharingUtils.postOnMainThread(mContext, () -> preference.setOrder(order));
            }
@@ -276,11 +276,12 @@ public class AudioSharingDeviceVolumeGroupController extends AudioSharingBasePre

    @Override
    public void onDeviceAdded(Preference preference) {
        if (!(preference instanceof AudioSharingDeviceVolumePreference)) {
        if (!(preference instanceof AudioSharingDeviceVolumePreference
                || preference instanceof AudioSharingDeviceVolumeSliderPreference)) {
            Log.d(TAG, "Skip onDeviceAdded, invalid preference type");
            return;
        }
        var volumePref = (AudioSharingDeviceVolumePreference) preference;
        var volumePref = preference;
        mVolumePreferences.add(volumePref);
        AudioSharingUtils.postOnMainThread(mContext, () -> {
            if (mPreferenceGroup != null) {
@@ -290,7 +291,7 @@ public class AudioSharingDeviceVolumeGroupController extends AudioSharingBasePre
                mPreferenceGroup.addPreference(volumePref);
            }
        });
        CachedBluetoothDevice cachedDevice = volumePref.getCachedDevice();
        CachedBluetoothDevice cachedDevice = getCachedDevice(volumePref);
        String address = cachedDevice.getDevice() == null ? "null"
                : cachedDevice.getDevice().getAnonymizedAddress();
        int order = getPreferenceOrderForDevice(cachedDevice);
@@ -300,7 +301,7 @@ public class AudioSharingDeviceVolumeGroupController extends AudioSharingBasePre
        // If the volume is invalid, try to get the volume from AudioManager.STREAM_MUSIC
        int finalVolume = getAudioVolumeIfNeeded(volume);
        Log.d(TAG, "onDeviceAdded: set volume to " + finalVolume + " for " + address);
        AudioSharingUtils.postOnMainThread(mContext, () -> volumePref.setProgress(finalVolume));
        AudioSharingUtils.postOnMainThread(mContext, () -> setValue(volumePref, finalVolume));
    }

    @Override
@@ -477,4 +478,19 @@ public class AudioSharingDeviceVolumeGroupController extends AudioSharingBasePre
                ? 0
                : 1;
    }
    private CachedBluetoothDevice getCachedDevice(Preference pref) {
        if (com.android.settings.flags.Flags.enableBluetoothSettingsExpressiveDesign()) {
            return ((AudioSharingDeviceVolumeSliderPreference) pref).getCachedDevice();
        } else {
            return ((AudioSharingDeviceVolumePreference) pref).getCachedDevice();
        }
    }

    private void setValue(Preference pref, int value) {
        if (com.android.settings.flags.Flags.enableBluetoothSettingsExpressiveDesign()) {
            ((AudioSharingDeviceVolumeSliderPreference) pref).setValue(value);
        } else {
            ((AudioSharingDeviceVolumePreference) pref).setProgress(value);
        }
    }
}
+175 −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.settings.connecteddevice.audiosharing;

import android.app.settings.SettingsEnums;
import android.bluetooth.BluetoothCsipSetCoordinator;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.media.AudioManager;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.android.settings.bluetooth.Utils;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.VolumeControlProfile;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import com.android.settingslib.utils.ThreadUtils;
import com.android.settingslib.widget.SliderPreference;

public class AudioSharingDeviceVolumeSliderPreference extends SliderPreference {
    private static final String TAG = "AudioSharingVolPref";

    public static final int MIN_VOLUME = 0;
    public static final int MAX_VOLUME = 255;

    private final Context mContext;
    private final CachedBluetoothDevice mCachedDevice;
    @Nullable private final LocalBluetoothManager mBtManager;
    private MetricsFeatureProvider mMetricsFeatureProvider =
            FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();

    public AudioSharingDeviceVolumeSliderPreference(
            Context context, @NonNull CachedBluetoothDevice device) {
        super(context);
        mContext = context;
        mCachedDevice = device;
        mBtManager = Utils.getLocalBtManager(mContext);
    }

    @NonNull
    public CachedBluetoothDevice getCachedDevice() {
        return mCachedDevice;
    }

    /**
     * Initialize {@link AudioSharingDeviceVolumeSliderPreference}.
     *
     * <p>Need to be called after creating the preference.
     */
    public void initialize() {
        setMax(MAX_VOLUME);
        setMin(MIN_VOLUME);
        setUpdatesContinuously(false);
        setOnPreferenceChangeListener(
                (pref, value) -> {
                    handleProgressChange((int) value);
                    return true;
                }
        );
    }

    @Override
    public boolean equals(@Nullable Object o) {
        if ((o == null) || !(o instanceof AudioSharingDeviceVolumeSliderPreference)) {
            return false;
        }
        return mCachedDevice.equals(
                ((AudioSharingDeviceVolumeSliderPreference) o).mCachedDevice);
    }

    @Override
    public int hashCode() {
        return mCachedDevice.hashCode();
    }

    @Override
    @NonNull
    public String toString() {
        StringBuilder builder = new StringBuilder("Preference{");
        builder.append("preference=").append(super.toString());
        if (mCachedDevice.getDevice() != null) {
            builder.append(", device=").append(mCachedDevice.getDevice().getAnonymizedAddress());
        }
        builder.append("}");
        return builder.toString();
    }

    void onPreferenceAttributesChanged() {
        var unused = ThreadUtils.postOnBackgroundThread(() -> {
            String name = mCachedDevice.getName();
            AudioSharingUtils.postOnMainThread(mContext, () -> setTitle(name));
        });
    }

    private void handleProgressChange(int progress) {
        var unused =
                ThreadUtils.postOnBackgroundThread(
                        () -> {
                            int groupId = BluetoothUtils.getGroupId(mCachedDevice);
                            if (groupId != BluetoothCsipSetCoordinator.GROUP_ID_INVALID
                                    && groupId
                                            == BluetoothUtils.getPrimaryGroupIdForBroadcast(
                                                    mContext.getContentResolver(), mBtManager)) {
                                // Set media stream volume for primary buds, audio manager will
                                // update all buds volume in the audio sharing.
                                setAudioManagerStreamVolume(progress);
                            } else {
                                // Set buds volume for other buds.
                                setDeviceVolume(mCachedDevice.getDevice(), progress);
                            }
                        });
    }

    private void setDeviceVolume(@Nullable BluetoothDevice device, int progress) {
        if (device == null) {
            Log.d(TAG, "Skip set device volume, device is null");
            return;
        }
        VolumeControlProfile vc = mBtManager == null ? null
                : mBtManager.getProfileManager().getVolumeControlProfile();
        if (vc != null) {
            vc.setDeviceVolume(device, progress, /* isGroupOp= */ true);
            mMetricsFeatureProvider.action(
                    mContext,
                    SettingsEnums.ACTION_AUDIO_SHARING_CHANGE_MEDIA_DEVICE_VOLUME,
                    /* isPrimary= */ false);
            Log.d(
                    TAG,
                    "set device volume, device = "
                            + device.getAnonymizedAddress()
                            + " volume = "
                            + progress);
        }
    }

    private void setAudioManagerStreamVolume(int progress) {
        int seekbarRange =
                AudioSharingDeviceVolumeSliderPreference.MAX_VOLUME
                        - AudioSharingDeviceVolumeSliderPreference.MIN_VOLUME;
        try {
            AudioManager audioManager = mContext.getSystemService(AudioManager.class);
            int streamVolumeRange =
                    audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)
                            - audioManager.getStreamMinVolume(AudioManager.STREAM_MUSIC);
            int volume = Math.round((float) progress * streamVolumeRange / seekbarRange);
            audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0);
            mMetricsFeatureProvider.action(
                    mContext,
                    SettingsEnums.ACTION_AUDIO_SHARING_CHANGE_MEDIA_DEVICE_VOLUME,
                    /* isPrimary= */ true);
            Log.d(TAG, "set music stream volume, volume = " + progress);
        } catch (RuntimeException e) {
            Log.e(TAG, "Fail to setAudioManagerStreamVolumeForFallbackDevice, error = " + e);
        }
    }
}
+176 −51

File changed.

Preview size limit exceeded, changes collapsed.

+81 −17

File changed.

Preview size limit exceeded, changes collapsed.

Loading