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

Commit d53731b5 authored by Yiyi Shen's avatar Yiyi Shen
Browse files

Fix the audio mode status when missing onAudioModeChanged

When the bluetooth device updater misses onAudioModeChanged event, e.g.
on device detail page, and go back to connected device page, the mode is
not re-fetched and stay in wrong state, which leads to the wrong device
state on UI.

Bug: 411169765
Flag: EXEMPT bug fix
Test: atest
Change-Id: I2422a09d1dae517d761355dafdc2b5e9d6becd2b
parent 55864923
Loading
Loading
Loading
Loading
+26 −26
Original line number Diff line number Diff line
@@ -15,9 +15,10 @@
 */
package com.android.settings.bluetooth;

import static com.android.settingslib.Utils.isAudioModeOngoingCall;

import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.media.AudioManager;
import android.util.Log;

import androidx.preference.Preference;
@@ -28,43 +29,43 @@ import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.utils.ThreadUtils;

import java.util.concurrent.atomic.AtomicBoolean;

/** Controller to maintain available media Bluetooth devices */
public class AvailableMediaBluetoothDeviceUpdater extends BluetoothDeviceUpdater
        implements Preference.OnPreferenceClickListener {

    private static final String TAG = "AvailableMediaBluetoothDeviceUpdater";
    private static final boolean DBG = Log.isLoggable(BluetoothDeviceUpdater.TAG, Log.DEBUG);

    private static final String PREF_KEY_PREFIX = "available_media_bt_";

    private final AudioManager mAudioManager;
    private final LocalBluetoothManager mLocalBtManager;
    private int mAudioMode;
    private AtomicBoolean mIsOngoingCall = new AtomicBoolean(false);

    public AvailableMediaBluetoothDeviceUpdater(
            Context context,
            DevicePreferenceCallback devicePreferenceCallback,
            int metricsCategory) {
        super(context, devicePreferenceCallback, metricsCategory);
        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
        mLocalBtManager = Utils.getLocalBtManager(context);
        mAudioMode = mAudioManager.getMode();
        var unused =
                ThreadUtils.postOnBackgroundThread(
                        () -> mIsOngoingCall.set(isAudioModeOngoingCall(mContext)));
    }

    @Override
    public void onAudioModeChanged() {
        // TODO: move to background thread
        mAudioMode = mAudioManager.getMode();
        forceUpdate();
    /**
     * Set if the device is in ongoing call mode.
     *
     * <p>This should be set whe the activity is onStart and when audio mode is changed.
     */
    public void setIsOngoingCall(boolean isOngoingCall) {
        mIsOngoingCall.set(isOngoingCall);
    }

    @Override
    public boolean isFilterMatched(CachedBluetoothDevice cachedDevice) {
        final int currentAudioProfile;

        if (mAudioMode == AudioManager.MODE_RINGTONE
                || mAudioMode == AudioManager.MODE_IN_CALL
                || mAudioMode == AudioManager.MODE_IN_COMMUNICATION) {
        if (mIsOngoingCall.get()) {
            // in phone call
            currentAudioProfile = BluetoothProfile.HEADSET;
        } else {
@@ -75,6 +76,7 @@ public class AvailableMediaBluetoothDeviceUpdater extends BluetoothDeviceUpdater
        boolean isFilterMatched = false;
        if (isDeviceConnected(cachedDevice) && isDeviceInCachedDevicesList(cachedDevice)) {
            Log.d(TAG, "isFilterMatched() current audio profile : " + currentAudioProfile);
            String deviceName = cachedDevice.getName();

            // If device is LE Audio, it is compatible with HFP and A2DP.
            // It would show in Available Devices group if the audio sharing flag is disabled or
@@ -86,18 +88,16 @@ public class AvailableMediaBluetoothDeviceUpdater extends BluetoothDeviceUpdater
                                cachedDevice, mLocalBtManager)) {
                    Log.d(
                            TAG,
                            "Filter out device : "
                                    + cachedDevice.getName()
                                    + ", it is in audio sharing.");
                            "isFilterMatched() device : "
                                    + deviceName
                                    + ", isFilterMatched : false, in audio sharing.");
                    return false;

                } else {
                    Log.d(
                            TAG,
                            "isFilterMatched() device : "
                                    + cachedDevice.getName()
                                    + ", the LE Audio profile is connected and not in sharing "
                                    + "if broadcast enabled.");
                                    + deviceName
                                    + ", isFilterMatched : true, the LEA profile is connected.");
                    return true;
                }
            }
@@ -108,8 +108,8 @@ public class AvailableMediaBluetoothDeviceUpdater extends BluetoothDeviceUpdater
                Log.d(
                        TAG,
                        "isFilterMatched() device : "
                                + cachedDevice.getName()
                                + ", the Hearing Aid profile is connected.");
                                + deviceName
                                + ", isFilterMatched : true, the HA profile is connected.");
                return true;
            }

@@ -130,7 +130,7 @@ public class AvailableMediaBluetoothDeviceUpdater extends BluetoothDeviceUpdater
            Log.d(
                    TAG,
                    "isFilterMatched() device : "
                            + cachedDevice.getName()
                            + deviceName
                            + ", isFilterMatched : "
                            + isFilterMatched);
        }
+40 −36
Original line number Diff line number Diff line
@@ -15,52 +15,55 @@
 */
package com.android.settings.bluetooth;

import static com.android.settingslib.Utils.isAudioModeOngoingCall;

import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.media.AudioManager;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.preference.Preference;

import com.android.settings.connecteddevice.DevicePreferenceCallback;
import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.utils.ThreadUtils;

/**
 * Controller to maintain connected bluetooth devices
 */
import java.util.concurrent.atomic.AtomicBoolean;

/** Controller to maintain connected bluetooth devices */
public class ConnectedBluetoothDeviceUpdater extends BluetoothDeviceUpdater {

    private static final String TAG = "ConnBluetoothDeviceUpdater";
    private static final boolean DBG = Log.isLoggable(BluetoothDeviceUpdater.TAG, Log.DEBUG);

    private static final String PREF_KEY_PREFIX = "connected_bt_";
    private AtomicBoolean mIsOngoingCall = new AtomicBoolean(false);

    private final AudioManager mAudioManager;
    private int mAudioMode;

    public ConnectedBluetoothDeviceUpdater(Context context,
            DevicePreferenceCallback devicePreferenceCallback, int metricsCategory) {
    public ConnectedBluetoothDeviceUpdater(
            @NonNull Context context,
            @NonNull DevicePreferenceCallback devicePreferenceCallback,
            int metricsCategory) {
        super(context, devicePreferenceCallback, metricsCategory);
        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
        mAudioMode = mAudioManager.getMode();
        var unused =
                ThreadUtils.postOnBackgroundThread(
                        () -> mIsOngoingCall.set(isAudioModeOngoingCall(mContext)));
    }

    @Override
    public void onAudioModeChanged() {
        // TODO: move to background thread
        mAudioMode = mAudioManager.getMode();
        forceUpdate();
    /**
     * Set if the device is in ongoing call mode.
     *
     * <p>This should be set whe the activity is onStart and when audio mode is changed.
     */
    public void setIsOngoingCall(boolean isOngoingCall) {
        mIsOngoingCall.set(isOngoingCall);
    }

    @Override
    public boolean isFilterMatched(CachedBluetoothDevice cachedDevice) {
        final int currentAudioProfile;

        if (mAudioMode == AudioManager.MODE_RINGTONE
                || mAudioMode == AudioManager.MODE_IN_CALL
                || mAudioMode == AudioManager.MODE_IN_COMMUNICATION) {
        if (mIsOngoingCall.get()) {
            // in phone call
            currentAudioProfile = BluetoothProfile.HEADSET;
        } else {
@@ -70,18 +73,19 @@ public class ConnectedBluetoothDeviceUpdater extends BluetoothDeviceUpdater {

        boolean isFilterMatched = false;
        if (isDeviceConnected(cachedDevice) && isDeviceInCachedDevicesList(cachedDevice)) {
            if (DBG) {
            Log.d(TAG, "isFilterMatched() current audio profile : " + currentAudioProfile);
            }
            String deviceName = cachedDevice.getName();

            // If device is Hearing Aid or LE Audio, it is compatible with HFP and A2DP.
            // It would not show in Connected Devices group.
            if (cachedDevice.isConnectedAshaHearingAidDevice()
                    || cachedDevice.isConnectedLeAudioDevice()
                    || cachedDevice.hasConnectedLeAudioMemberDevice()) {
                if (DBG) {
                    Log.d(TAG, "isFilterMatched() device : " + cachedDevice.getName()
                            + ", isFilterMatched : false, ha or lea device");
                }
                Log.d(
                        TAG,
                        "isFilterMatched() device : "
                                + deviceName
                                + ", isFilterMatched : false, HA or LEA profile connected");
                return false;
            }
            // According to the current audio profile type,
@@ -99,16 +103,16 @@ public class ConnectedBluetoothDeviceUpdater extends BluetoothDeviceUpdater {
                    isFilterMatched = !cachedDevice.isConnectedHfpDevice();
                    break;
            }
            if (DBG) {
                Log.d(TAG, "isFilterMatched() device : " +
                        cachedDevice.getName() + ", isFilterMatched : " + isFilterMatched);
            }
            Log.d(
                    TAG,
                    "isFilterMatched() device : "
                            + deviceName
                            + ", isFilterMatched : "
                            + isFilterMatched);
        }
        if (BluetoothUtils.isExclusivelyManagedBluetoothDevice(mContext,
                cachedDevice.getDevice())) {
            if (DBG) {
        if (BluetoothUtils.isExclusivelyManagedBluetoothDevice(
                mContext, cachedDevice.getDevice())) {
            Log.d(TAG, "isFilterMatched() hide BluetoothDevice with exclusive manager");
            }
            return false;
        }
        return isFilterMatched;
+63 −29
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.annotation.WorkerThread;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
@@ -58,6 +59,7 @@ import com.android.settingslib.utils.ThreadUtils;

import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * Controller to maintain the {@link androidx.preference.PreferenceGroup} for all available media
@@ -76,6 +78,7 @@ public class AvailableMediaDeviceGroupController extends BasePreferenceControlle
    @Nullable private BluetoothDeviceUpdater mBluetoothDeviceUpdater;
    @Nullable private FragmentManager mFragmentManager;
    @Nullable private AudioSharingDialogHandler mDialogHandler;
    private AtomicBoolean mIsOngoingCall = new AtomicBoolean(false);
    private BluetoothLeBroadcast.Callback mBroadcastCallback =
            new BluetoothLeBroadcast.Callback() {
                @Override
@@ -131,8 +134,7 @@ public class AvailableMediaDeviceGroupController extends BasePreferenceControlle
                public void onSourceFound(@NonNull BluetoothLeBroadcastMetadata source) {}

                @Override
                public void onSourceAdded(
                        @NonNull BluetoothDevice sink, int sourceId, int reason) {
                public void onSourceAdded(@NonNull BluetoothDevice sink, int sourceId, int reason) {
                    Log.d(TAG, "onSourceAdded: update media device list.");
                    if (mBluetoothDeviceUpdater != null) {
                        mBluetoothDeviceUpdater.forceUpdate();
@@ -191,13 +193,25 @@ public class AvailableMediaDeviceGroupController extends BasePreferenceControlle

    @Override
    public void onStart(@NonNull LifecycleOwner owner) {
        if (isAvailable()) {
            updateTitle();
        }
        if (mBtManager == null) {
            Log.d(TAG, "onStart() Bluetooth is not supported on this device");
            return;
        }
        if (!isAvailable()) {
            Log.d(TAG, "onStart() Bluetooth feature is not available on this device");
            return;
        }
        var unused =
                ThreadUtils.postOnBackgroundThread(
                        () -> {
                            boolean isOngoingCall = isAudioModeOngoingCall(mContext);
                            mIsOngoingCall.set(isOngoingCall);
                            if (mBluetoothDeviceUpdater
                                    instanceof AvailableMediaBluetoothDeviceUpdater updater) {
                                updater.setIsOngoingCall(isOngoingCall);
                            }
                            updateTitle();
                        });
        if (BluetoothUtils.isAudioSharingUIAvailable(mContext)) {
            registerAudioSharingCallbacks();
        }
@@ -214,6 +228,10 @@ public class AvailableMediaDeviceGroupController extends BasePreferenceControlle
            Log.d(TAG, "onStop() Bluetooth is not supported on this device");
            return;
        }
        if (!isAvailable()) {
            Log.d(TAG, "onStop() Bluetooth feature is not available on this device");
            return;
        }
        if (BluetoothUtils.isAudioSharingUIAvailable(mContext)) {
            unregisterAudioSharingCallbacks();
        }
@@ -278,7 +296,8 @@ public class AvailableMediaDeviceGroupController extends BasePreferenceControlle
                ((BluetoothDevicePreference) preference).getBluetoothDevice();
        if (BluetoothUtils.isAudioSharingUIAvailable(mContext) && mDialogHandler != null) {
            mDialogHandler.handleDeviceConnected(cachedDevice, /* userTriggered= */ true);
            FeatureFactory.getFeatureFactory().getMetricsFeatureProvider()
            FeatureFactory.getFeatureFactory()
                    .getMetricsFeatureProvider()
                    .action(mContext, SettingsEnums.ACTION_MEDIA_DEVICE_CLICK);
        } else {
            cachedDevice.setActive();
@@ -314,7 +333,25 @@ public class AvailableMediaDeviceGroupController extends BasePreferenceControlle

    @Override
    public void onAudioModeChanged() {
        var unused =
                ThreadUtils.postOnBackgroundThread(
                        () -> {
                            boolean isOngoingCall = isAudioModeOngoingCall(mContext);
                            mIsOngoingCall.set(isOngoingCall);
                            Log.d(TAG, "onAudioModeChanged, in call mode = " + isOngoingCall);
                            if (mBluetoothDeviceUpdater
                                    instanceof AvailableMediaBluetoothDeviceUpdater updater) {
                                updater.setIsOngoingCall(isOngoingCall);
                            }
                            updateTitle();
                            mContext.getMainExecutor()
                                    .execute(
                                            () -> {
                                                if (mBluetoothDeviceUpdater != null) {
                                                    mBluetoothDeviceUpdater.forceUpdate();
                                                }
                                            });
                        });
    }

    @Override
@@ -330,14 +367,12 @@ public class AvailableMediaDeviceGroupController extends BasePreferenceControlle
        }
    }

    @WorkerThread
    private void updateTitle() {
        if (mPreferenceGroup == null) return;
        var unused =
                ThreadUtils.postOnBackgroundThread(
                        () -> {
        Log.d(TAG, "updateTitle, check current status");
        int titleResId;
                            if (isAudioModeOngoingCall(mContext)) {
        if (mIsOngoingCall.get()) {
            // in phone call
            titleResId = R.string.connected_device_call_device_title;
        } else if (BluetoothUtils.isAudioSharingUIAvailable(mContext)
@@ -356,7 +391,6 @@ public class AvailableMediaDeviceGroupController extends BasePreferenceControlle
                                mPreferenceGroup.setTitle(titleResId);
                            }
                        });
                        });
    }

    private void registerAudioSharingCallbacks() {
+49 −6
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@
package com.android.settings.connecteddevice;

import static com.android.settings.connecteddevice.display.ExternalDisplaySettingsConfiguration.isExternalDisplaySettingsPageEnabled;
import static com.android.settingslib.Utils.isAudioModeOngoingCall;

import android.content.Context;
import android.content.pm.PackageManager;
@@ -46,6 +47,7 @@ import com.android.settings.flags.FeatureFlags;
import com.android.settings.flags.FeatureFlagsImpl;
import com.android.settings.overlay.DockUpdaterFeatureProvider;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.bluetooth.BluetoothCallback;
import com.android.settingslib.bluetooth.BluetoothDeviceFilter;
import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
@@ -54,6 +56,7 @@ import com.android.settingslib.core.lifecycle.LifecycleObserver;
import com.android.settingslib.core.lifecycle.events.OnStart;
import com.android.settingslib.core.lifecycle.events.OnStop;
import com.android.settingslib.search.SearchIndexableRaw;
import com.android.settingslib.utils.ThreadUtils;

import java.util.List;

@@ -62,8 +65,12 @@ import java.util.List;
 * connected devices. It uses {@link DevicePreferenceCallback} to add/remove {@link Preference}
 */
public class ConnectedDeviceGroupController extends BasePreferenceController
        implements PreferenceControllerMixin, LifecycleObserver, OnStart, OnStop,
        DevicePreferenceCallback {
        implements PreferenceControllerMixin,
                LifecycleObserver,
                OnStart,
                OnStop,
                DevicePreferenceCallback,
                BluetoothCallback {

    private static final String KEY = "connected_device_list";
    private static final String TAG = "ConnectedDeviceGroupController";
@@ -96,10 +103,25 @@ public class ConnectedDeviceGroupController extends BasePreferenceController
            mExternalDisplayUpdater.refreshPreference();
        }

        if (mBluetoothDeviceUpdater != null) {
            var unused =
                    ThreadUtils.postOnBackgroundThread(
                            () -> {
                                boolean isOngoingCall = isAudioModeOngoingCall(mContext);
                                if (mBluetoothDeviceUpdater
                                        instanceof ConnectedBluetoothDeviceUpdater updater) {
                                    updater.setIsOngoingCall(isOngoingCall);
                                }
                                mContext.getMainExecutor()
                                        .execute(
                                                () -> {
                                                    if (mBluetoothDeviceUpdater != null) {
                                                        mBluetoothDeviceUpdater.registerCallback();
                                                        mBluetoothDeviceUpdater.refreshPreference();
                                                    }
                                                });
                            });
        }

        if (mConnectedUsbDeviceUpdater != null) {
            mConnectedUsbDeviceUpdater.registerCallback();
@@ -203,6 +225,27 @@ public class ConnectedDeviceGroupController extends BasePreferenceController
        }
    }

    @Override
    public void onAudioModeChanged() {
        var unused =
                ThreadUtils.postOnBackgroundThread(
                        () -> {
                            boolean isOngoingCall = isAudioModeOngoingCall(mContext);
                            Log.d(TAG, "onAudioModeChanged, in call mode = " + isOngoingCall);
                            if (mBluetoothDeviceUpdater
                                    instanceof ConnectedBluetoothDeviceUpdater updater) {
                                updater.setIsOngoingCall(isOngoingCall);
                            }
                            mContext.getMainExecutor()
                                    .execute(
                                            () -> {
                                                if (mBluetoothDeviceUpdater != null) {
                                                    mBluetoothDeviceUpdater.forceUpdate();
                                                }
                                            });
                        });
    }

    @VisibleForTesting
    void init(@Nullable ExternalDisplayUpdater externalDisplayUpdater,
            BluetoothDeviceUpdater bluetoothDeviceUpdater,
@@ -262,8 +305,8 @@ public class ConnectedDeviceGroupController extends BasePreferenceController
    }

    private boolean hasUsiStylusFeature() {
        if (!FeatureFlagUtils.isEnabled(mContext,
                FeatureFlagUtils.SETTINGS_SHOW_STYLUS_PREFERENCES)) {
        if (!FeatureFlagUtils.isEnabled(
                mContext, FeatureFlagUtils.SETTINGS_SHOW_STYLUS_PREFERENCES)) {
            return false;
        }

+41 −32

File changed.

Preview size limit exceeded, changes collapsed.

Loading