Merge "[Audiosharing] Show/hide audio sharing settings based on BT state." into main
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingBasePreferenceController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingBasePreferenceController.java
index 21f1c0e..90cf779 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.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 @@
@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 7f90ceb..52a8f18 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDashboardFragment.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDashboardFragment.java
@@ -93,14 +93,14 @@
}
@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 90054d4..bdaa534 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeGroupController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeGroupController.java
@@ -27,7 +27,6 @@
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.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 @@
@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 @@
@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 @@
}
@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 8336691..36f66ff 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreferenceController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingNamePreferenceController.java
@@ -19,16 +19,13 @@
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 @@
@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 96a5579..469a387 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.OnCheckedChangeListener;
import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
@@ -58,19 +59,20 @@
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 @@
intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothDevice.ERROR);
mSwitchBar.setChecked(isBroadcasting());
mSwitchBar.setEnabled(adapterState == BluetoothAdapter.STATE_ON);
+ mListener.onSwitchBarChanged();
}
};
@@ -346,15 +349,19 @@
}
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 @@
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 a6adf8a..b3d676c 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.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.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 @@
/** 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 @@
@Override
public void onStart(@NonNull LifecycleOwner owner) {
+ super.onStart(owner);
if (mLocalBtManager != null) {
mLocalBtManager.getEventManager().registerCallback(this);
}
@@ -93,25 +94,46 @@
@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 f80fdab..f47526f 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.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 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 void onActiveDeviceChanged(
@Nullable CachedBluetoothDevice activeDevice, int bluetoothProfile) {
if (bluetoothProfile == BluetoothProfile.LE_AUDIO) {
- updateVisibility(isBroadcasting());
+ updateVisibility();
}
}
};
@@ -63,14 +61,15 @@
@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 @@
}
@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 0000000..0b94061
--- /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();
+ }
+}