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();
+    }
+}