From bdc870a4c11692f804a820b2def397074e6108c2 Mon Sep 17 00:00:00 2001 From: Yiyi Shen Date: Tue, 28 Nov 2023 17:10:55 +0800 Subject: [PATCH] [Audiosharing] Show/hide audio sharing settings based on BT state. Flagged with enable_le_audio_sharing Bug: 305620450 Test: atest Change-Id: I1af8ae268194cfca8e03cc49151b68a25717e89a --- .../AudioSharingBasePreferenceController.java | 44 +++++++--- .../AudioSharingDashboardFragment.java | 14 +-- ...dioSharingDeviceVolumeGroupController.java | 13 +-- .../AudioSharingNamePreferenceController.java | 7 +- .../AudioSharingSwitchBarController.java | 31 ++++--- .../CallsAndAlarmsPreferenceController.java | 52 ++++++++---- .../AudioStreamsCategoryController.java | 22 +++-- .../AudioSharingSwitchBarControllerTest.java | 85 +++++++++++++++++++ 8 files changed, 205 insertions(+), 63 deletions(-) create mode 100644 tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingBasePreferenceController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingBasePreferenceController.java index 21f1c0e4f73..90cf77998df 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingBasePreferenceController.java +++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingBasePreferenceController.java @@ -16,8 +16,12 @@ package com.android.settings.connecteddevice.audiosharing; +import android.bluetooth.BluetoothAdapter; import android.content.Context; +import androidx.annotation.NonNull; +import androidx.lifecycle.DefaultLifecycleObserver; +import androidx.lifecycle.LifecycleOwner; import androidx.preference.Preference; import androidx.preference.PreferenceScreen; @@ -26,14 +30,18 @@ import com.android.settings.core.BasePreferenceController; import com.android.settings.flags.Flags; import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast; import com.android.settingslib.bluetooth.LocalBluetoothManager; +import com.android.settingslib.utils.ThreadUtils; -public abstract class AudioSharingBasePreferenceController extends BasePreferenceController { +public abstract class AudioSharingBasePreferenceController extends BasePreferenceController + implements DefaultLifecycleObserver { + private final BluetoothAdapter mBluetoothAdapter; private final LocalBluetoothManager mBtManager; protected final LocalBluetoothLeBroadcast mBroadcast; protected Preference mPreference; public AudioSharingBasePreferenceController(Context context, String preferenceKey) { super(context, preferenceKey); + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); mBtManager = Utils.getLocalBtManager(context); mBroadcast = mBtManager == null @@ -43,28 +51,40 @@ public abstract class AudioSharingBasePreferenceController extends BasePreferenc @Override public int getAvailabilityStatus() { - return mBtManager != null && Flags.enableLeAudioSharing() - ? AVAILABLE - : UNSUPPORTED_ON_DEVICE; + return Flags.enableLeAudioSharing() ? AVAILABLE : UNSUPPORTED_ON_DEVICE; } @Override public void displayPreference(PreferenceScreen screen) { super.displayPreference(screen); mPreference = screen.findPreference(getPreferenceKey()); - updateVisibility(isBroadcasting()); } - /** - * Update the visibility of the preference. - * - * @param isVisible the latest visibility state for the preference. - */ - public void updateVisibility(boolean isVisible) { - mPreference.setVisible(isVisible); + @Override + public void onStart(@NonNull LifecycleOwner owner) { + if (isAvailable()) { + updateVisibility(); + } + } + + /** Update the visibility of the preference. */ + protected void updateVisibility() { + if (mPreference != null) { + var unused = + ThreadUtils.postOnBackgroundThread( + () -> { + boolean isVisible = isBroadcasting() && isBluetoothStateOn(); + ThreadUtils.postOnMainThread( + () -> mPreference.setVisible(isVisible)); + }); + } } protected boolean isBroadcasting() { return mBroadcast != null && mBroadcast.isEnabled(null); } + + protected boolean isBluetoothStateOn() { + return mBluetoothAdapter != null && mBluetoothAdapter.isEnabled(); + } } diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDashboardFragment.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDashboardFragment.java index 7f90cebcf46..52a8f18393e 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDashboardFragment.java +++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDashboardFragment.java @@ -93,14 +93,14 @@ public class AudioSharingDashboardFragment extends DashboardFragment } @Override - public void onSwitchBarChanged(boolean newState) { - updateVisibilityForAttachedPreferences(newState); + public void onSwitchBarChanged() { + updateVisibilityForAttachedPreferences(); } - private void updateVisibilityForAttachedPreferences(boolean isVisible) { - mAudioSharingDeviceVolumeGroupController.updateVisibility(isVisible); - mCallsAndAlarmsPreferenceController.updateVisibility(isVisible); - mAudioSharingNamePreferenceController.updateVisibility(isVisible); - mAudioStreamsCategoryController.updateVisibility(isVisible); + private void updateVisibilityForAttachedPreferences() { + mAudioSharingDeviceVolumeGroupController.updateVisibility(); + mCallsAndAlarmsPreferenceController.updateVisibility(); + mAudioSharingNamePreferenceController.updateVisibility(); + mAudioStreamsCategoryController.updateVisibility(); } } diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeGroupController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeGroupController.java index 90054d4b3f9..bdaa534e1d1 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeGroupController.java +++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeGroupController.java @@ -27,7 +27,6 @@ import android.util.Log; import androidx.annotation.NonNull; import androidx.fragment.app.FragmentManager; -import androidx.lifecycle.DefaultLifecycleObserver; import androidx.lifecycle.LifecycleOwner; import androidx.preference.Preference; import androidx.preference.PreferenceGroup; @@ -48,7 +47,7 @@ import java.util.concurrent.Executor; import java.util.concurrent.Executors; public class AudioSharingDeviceVolumeGroupController extends AudioSharingBasePreferenceController - implements DefaultLifecycleObserver, DevicePreferenceCallback { + implements DevicePreferenceCallback { private static final String TAG = "AudioSharingDeviceVolumeGroupController"; private static final String KEY = "audio_sharing_device_volume_group"; @@ -162,6 +161,7 @@ public class AudioSharingDeviceVolumeGroupController extends AudioSharingBasePre @Override public void onStart(@NonNull LifecycleOwner owner) { + super.onStart(owner); if (mAssistant == null) { Log.d(TAG, "onStart() Broadcast or assistant is not supported on this device"); return; @@ -176,6 +176,7 @@ public class AudioSharingDeviceVolumeGroupController extends AudioSharingBasePre @Override public void onStop(@NonNull LifecycleOwner owner) { + super.onStop(owner); if (mAssistant == null) { Log.d(TAG, "onStop() Broadcast or assistant is not supported on this device"); return; @@ -233,10 +234,12 @@ public class AudioSharingDeviceVolumeGroupController extends AudioSharingBasePre } @Override - public void updateVisibility(boolean isVisible) { - super.updateVisibility(isVisible); + public void updateVisibility() { if (mPreferenceGroup != null) { - mPreferenceGroup.setVisible(mPreferenceGroup.getPreferenceCount() > 0); + mPreferenceGroup.setVisible(false); + if (mPreferenceGroup.getPreferenceCount() > 0) { + super.updateVisibility(); + } } } diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreferenceController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreferenceController.java index 8336691042a..36f66ff01ee 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreferenceController.java +++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreferenceController.java @@ -19,16 +19,13 @@ package com.android.settings.connecteddevice.audiosharing; import android.content.Context; import androidx.annotation.NonNull; -import androidx.lifecycle.DefaultLifecycleObserver; import androidx.lifecycle.LifecycleOwner; import androidx.preference.Preference; import com.android.settings.widget.ValidatedEditTextPreference; public class AudioSharingNamePreferenceController extends AudioSharingBasePreferenceController - implements ValidatedEditTextPreference.Validator, - Preference.OnPreferenceChangeListener, - DefaultLifecycleObserver { + implements ValidatedEditTextPreference.Validator, Preference.OnPreferenceChangeListener { private static final String TAG = "AudioSharingNamePreferenceController"; @@ -59,11 +56,13 @@ public class AudioSharingNamePreferenceController extends AudioSharingBasePrefer @Override public void onStart(@NonNull LifecycleOwner owner) { + super.onStart(owner); // TODO } @Override public void onStop(@NonNull LifecycleOwner owner) { + super.onStop(owner); // TODO } } diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java index 96a55790e6e..469a387e025 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java +++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java @@ -31,6 +31,7 @@ import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; import androidx.lifecycle.DefaultLifecycleObserver; import androidx.lifecycle.LifecycleOwner; @@ -58,19 +59,20 @@ public class AudioSharingSwitchBarController extends BasePreferenceController private static final String PREF_KEY = "audio_sharing_main_switch"; interface OnSwitchBarChangedListener { - void onSwitchBarChanged(boolean newState); + void onSwitchBarChanged(); } private final SettingsMainSwitchBar mSwitchBar; private final BluetoothAdapter mBluetoothAdapter; - private final IntentFilter mIntentFilter; private final LocalBluetoothManager mBtManager; private final LocalBluetoothLeBroadcast mBroadcast; private final LocalBluetoothLeBroadcastAssistant mAssistant; private final Executor mExecutor; private final OnSwitchBarChangedListener mListener; private DashboardFragment mFragment; + @VisibleForTesting IntentFilter mIntentFilter; + @VisibleForTesting BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override @@ -80,6 +82,7 @@ public class AudioSharingSwitchBarController extends BasePreferenceController intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothDevice.ERROR); mSwitchBar.setChecked(isBroadcasting()); mSwitchBar.setEnabled(adapterState == BluetoothAdapter.STATE_ON); + mListener.onSwitchBarChanged(); } }; @@ -346,15 +349,19 @@ public class AudioSharingSwitchBarController extends BasePreferenceController } private void updateSwitch() { - ThreadUtils.postOnMainThread( - () -> { - boolean isBroadcasting = isBroadcasting(); - if (mSwitchBar.isChecked() != isBroadcasting) { - mSwitchBar.setChecked(isBroadcasting); - } - mSwitchBar.setEnabled(true); - mListener.onSwitchBarChanged(isBroadcasting); - }); + var unused = + ThreadUtils.postOnBackgroundThread( + () -> { + boolean isBroadcasting = isBroadcasting(); + ThreadUtils.postOnMainThread( + () -> { + if (mSwitchBar.isChecked() != isBroadcasting) { + mSwitchBar.setChecked(isBroadcasting); + } + mSwitchBar.setEnabled(true); + mListener.onSwitchBarChanged(); + }); + }); } private boolean isBroadcasting() { @@ -376,7 +383,7 @@ public class AudioSharingSwitchBarController extends BasePreferenceController TAG, "Add broadcast with broadcastId: " + broadcastMetadata.getBroadcastId() - + "to the device: " + + " to the device: " + sink.getAnonymizedAddress()); mAssistant.addSource(sink, broadcastMetadata, /* isGroupOp= */ false); } diff --git a/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsPreferenceController.java b/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsPreferenceController.java index a6adf8a2cd6..b3d676cbfd8 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsPreferenceController.java +++ b/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsPreferenceController.java @@ -22,7 +22,6 @@ import android.content.Context; import android.util.Log; import androidx.annotation.NonNull; -import androidx.lifecycle.DefaultLifecycleObserver; import androidx.lifecycle.LifecycleOwner; import androidx.preference.PreferenceScreen; @@ -31,6 +30,7 @@ import com.android.settings.dashboard.DashboardFragment; import com.android.settingslib.bluetooth.BluetoothCallback; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.LocalBluetoothManager; +import com.android.settingslib.utils.ThreadUtils; import java.util.ArrayList; import java.util.HashMap; @@ -39,7 +39,7 @@ import java.util.Map; /** PreferenceController to control the dialog to choose the active device for calls and alarms */ public class CallsAndAlarmsPreferenceController extends AudioSharingBasePreferenceController - implements BluetoothCallback, DefaultLifecycleObserver { + implements BluetoothCallback { private static final String TAG = "CallsAndAlarmsPreferenceController"; private static final String PREF_KEY = "calls_and_alarms"; @@ -86,6 +86,7 @@ public class CallsAndAlarmsPreferenceController extends AudioSharingBasePreferen @Override public void onStart(@NonNull LifecycleOwner owner) { + super.onStart(owner); if (mLocalBtManager != null) { mLocalBtManager.getEventManager().registerCallback(this); } @@ -93,25 +94,46 @@ public class CallsAndAlarmsPreferenceController extends AudioSharingBasePreferen @Override public void onStop(@NonNull LifecycleOwner owner) { + super.onStop(owner); if (mLocalBtManager != null) { mLocalBtManager.getEventManager().unregisterCallback(this); } } @Override - public void updateVisibility(boolean isVisible) { - super.updateVisibility(isVisible); - if (isVisible && mPreference != null) { - updateDeviceItemsInSharingSession(); - // mDeviceItemsInSharingSession is ordered. The active device is the first place if - // exits. - if (!mDeviceItemsInSharingSession.isEmpty() - && mDeviceItemsInSharingSession.get(0).isActive()) { - mPreference.setSummary(mDeviceItemsInSharingSession.get(0).getName()); - } else { - mPreference.setSummary(""); - } - } + public void updateVisibility() { + if (mPreference == null) return; + var unused = + ThreadUtils.postOnBackgroundThread( + () -> { + boolean isVisible = isBroadcasting() && isBluetoothStateOn(); + if (!isVisible) { + ThreadUtils.postOnMainThread(() -> mPreference.setVisible(false)); + } else { + updateDeviceItemsInSharingSession(); + // mDeviceItemsInSharingSession is ordered. The active device is the + // first + // place if exits. + if (!mDeviceItemsInSharingSession.isEmpty() + && mDeviceItemsInSharingSession.get(0).isActive()) { + ThreadUtils.postOnMainThread( + () -> { + mPreference.setVisible(true); + mPreference.setSummary( + mDeviceItemsInSharingSession + .get(0) + .getName()); + }); + } else { + ThreadUtils.postOnMainThread( + () -> { + mPreference.setVisible(true); + mPreference.setSummary( + "No active device in sharing"); + }); + } + } + }); } @Override diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsCategoryController.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsCategoryController.java index f80fdab4ffd..f47526f5fa6 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsCategoryController.java +++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsCategoryController.java @@ -22,7 +22,6 @@ import android.content.Context; import android.util.Log; import androidx.annotation.NonNull; -import androidx.lifecycle.DefaultLifecycleObserver; import androidx.lifecycle.LifecycleOwner; import com.android.settings.bluetooth.Utils; @@ -38,8 +37,7 @@ import com.android.settingslib.utils.ThreadUtils; import java.util.concurrent.Executor; import java.util.concurrent.Executors; -public class AudioStreamsCategoryController extends AudioSharingBasePreferenceController - implements DefaultLifecycleObserver { +public class AudioStreamsCategoryController extends AudioSharingBasePreferenceController { private static final String TAG = "AudioStreamsCategoryController"; private static final boolean DEBUG = BluetoothUtils.D; private final LocalBluetoothManager mLocalBtManager; @@ -50,7 +48,7 @@ public class AudioStreamsCategoryController extends AudioSharingBasePreferenceCo public void onActiveDeviceChanged( @Nullable CachedBluetoothDevice activeDevice, int bluetoothProfile) { if (bluetoothProfile == BluetoothProfile.LE_AUDIO) { - updateVisibility(isBroadcasting()); + updateVisibility(); } } }; @@ -63,14 +61,15 @@ public class AudioStreamsCategoryController extends AudioSharingBasePreferenceCo @Override public void onStart(@NonNull LifecycleOwner owner) { + super.onStart(owner); if (mLocalBtManager != null) { mLocalBtManager.getEventManager().registerCallback(mBluetoothCallback); } - updateVisibility(isBroadcasting()); } @Override public void onStop(@NonNull LifecycleOwner owner) { + super.onStop(owner); if (mLocalBtManager != null) { mLocalBtManager.getEventManager().unregisterCallback(mBluetoothCallback); } @@ -84,21 +83,28 @@ public class AudioStreamsCategoryController extends AudioSharingBasePreferenceCo } @Override - public void updateVisibility(boolean isBroadcasting) { + public void updateVisibility() { + if (mPreference == null) return; mExecutor.execute( () -> { boolean hasActiveLe = AudioSharingUtils.getActiveSinkOnAssistant(mLocalBtManager).isPresent(); + boolean isBroadcasting = isBroadcasting(); + boolean isBluetoothOn = isBluetoothStateOn(); if (DEBUG) { Log.d( TAG, "updateVisibility() isBroadcasting : " + isBroadcasting + " hasActiveLe : " - + hasActiveLe); + + hasActiveLe + + " is BT on : " + + isBluetoothOn); } ThreadUtils.postOnMainThread( - () -> super.updateVisibility(hasActiveLe && !isBroadcasting)); + () -> + mPreference.setVisible( + isBluetoothOn && hasActiveLe && !isBroadcasting)); }); } } diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java new file mode 100644 index 00000000000..0b9406124e7 --- /dev/null +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarControllerTest.java @@ -0,0 +1,85 @@ +/* + * 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 static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.verify; +import static org.robolectric.Shadows.shadowOf; + +import android.bluetooth.BluetoothAdapter; +import android.content.Context; +import android.content.Intent; +import android.os.Looper; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.widget.Switch; + +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.flags.Flags; +import com.android.settings.widget.SettingsMainSwitchBar; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.RobolectricTestRunner; + +@RunWith(RobolectricTestRunner.class) +public class AudioSharingSwitchBarControllerTest { + @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); + + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + + @Spy Context mContext = ApplicationProvider.getApplicationContext(); + @Mock private Switch mSwitch; + + private SettingsMainSwitchBar mSwitchBar; + private AudioSharingSwitchBarController mController; + private AudioSharingSwitchBarController.OnSwitchBarChangedListener mListener; + private boolean mOnSwitchBarChanged; + + @Before + public void setUp() { + mSwitchBar = new SettingsMainSwitchBar(mContext); + mOnSwitchBarChanged = false; + mListener = () -> mOnSwitchBarChanged = true; + mController = new AudioSharingSwitchBarController(mContext, mSwitchBar, mListener); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_ENABLE_LE_AUDIO_SHARING) + public void bluetoothOff_switchDisabled() { + mContext.registerReceiver( + mController.mReceiver, + mController.mIntentFilter, + Context.RECEIVER_EXPORTED_UNAUDITED); + Intent intent = new Intent(BluetoothAdapter.ACTION_STATE_CHANGED); + intent.putExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF); + mContext.sendBroadcast(intent); + shadowOf(Looper.getMainLooper()).idle(); + verify(mSwitch).setEnabled(false); + assertThat(mOnSwitchBarChanged).isTrue(); + } +}