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

Commit 283505bd authored by Haijie Hong's avatar Haijie Hong
Browse files

Use SliderPreference in audio sharing dashboard

Test: local tested
Flag: com.android.settingslib.widget.theme.flags.is_expressive_design_enabled
Bug: 409185858
Change-Id: I2e20151363791512863dede41893bcc0b1f2e46d
parent 7fdf11da
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