[Audiosharing] Impl audio sharing main switch.
Start/stop broadcast when no eligible buds connected.
Flagged with enable_le_audio_sharing
Bug: 305620450
Test: Manual
Change-Id: I04359c0954e336ceb1a89a7836199b5be0b5e0c5
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragment.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragment.java
index 5b99907..1fd0b87 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragment.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragment.java
@@ -38,6 +38,26 @@
public class AudioSharingDialogFragment extends InstrumentedDialogFragment {
private static final String TAG = "AudioSharingDialog";
+ private static final String BUNDLE_KEY_DEVICE_NAMES = "bundle_key_device_names";
+
+ // The host creates an instance of this dialog fragment must implement this interface to receive
+ // event callbacks.
+ public interface DialogEventListener {
+ /**
+ * Called when users click the device item for sharing in the dialog.
+ *
+ * @param position The position of the item clicked.
+ */
+ void onItemClick(int position);
+
+ /**
+ * Called when users click the cancel button in the dialog.
+ */
+ void onCancelClick();
+ }
+
+ private static DialogEventListener sListener;
+
private View mRootView;
@Override
@@ -50,40 +70,59 @@
*
* @param host The Fragment this dialog will be hosted.
*/
- public static void show(Fragment host) {
+ public static void show(
+ Fragment host, ArrayList<String> deviceNames, DialogEventListener listener) {
if (!Flags.enableLeAudioSharing()) return;
final FragmentManager manager = host.getChildFragmentManager();
+ sListener = listener;
if (manager.findFragmentByTag(TAG) == null) {
- final AudioSharingDialogFragment dialog = new AudioSharingDialogFragment();
+ final Bundle bundle = new Bundle();
+ bundle.putStringArrayList(BUNDLE_KEY_DEVICE_NAMES, deviceNames);
+ AudioSharingDialogFragment dialog = new AudioSharingDialogFragment();
+ dialog.setArguments(bundle);
dialog.show(manager, TAG);
}
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
+ Bundle arguments = requireArguments();
+ ArrayList<String> deviceNames = arguments.getStringArrayList(BUNDLE_KEY_DEVICE_NAMES);
final AlertDialog.Builder builder =
- new AlertDialog.Builder(getActivity()).setTitle("Share audio");
+ new AlertDialog.Builder(getActivity()).setTitle("Share audio").setCancelable(false);
mRootView =
LayoutInflater.from(builder.getContext())
.inflate(R.layout.dialog_audio_sharing, /* parent= */ null);
- // TODO: use real subtitle according to device count.
TextView subTitle1 = mRootView.findViewById(R.id.share_audio_subtitle1);
TextView subTitle2 = mRootView.findViewById(R.id.share_audio_subtitle2);
- subTitle1.setText("2 devices connected");
- subTitle2.setText("placeholder");
+ if (deviceNames.isEmpty()) {
+ subTitle1.setVisibility(View.INVISIBLE);
+ subTitle2.setText("To start sharing audio, connect headphones that support LE audio");
+ builder.setNegativeButton(
+ "Close",
+ (dialog, which) -> {
+ sListener.onCancelClick();
+ });
+ } else if (deviceNames.size() == 1) {
+ // TODO: add real impl
+ subTitle1.setText("1 devices connected");
+ subTitle2.setText("placeholder");
+ } else {
+ // TODO: add real impl
+ subTitle1.setText("2 devices connected");
+ subTitle2.setText("placeholder");
+ }
RecyclerView recyclerView = mRootView.findViewById(R.id.btn_list);
- // TODO: use real audio sharing device list.
- ArrayList<String> devices = new ArrayList<>();
- devices.add("Buds 1");
- devices.add("Buds 2");
recyclerView.setAdapter(
new AudioSharingDeviceAdapter(
- devices,
+ deviceNames,
(int position) -> {
- // TODO: add on click callback.
+ sListener.onItemClick(position);
}));
recyclerView.setLayoutManager(
new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false));
- return builder.setView(mRootView).create();
+ AlertDialog dialog = builder.setView(mRootView).create();
+ dialog.setCanceledOnTouchOutside(false);
+ return dialog;
}
}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java
index a375a3c..bd8027c 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingSwitchBarController.java
@@ -16,6 +16,8 @@
package com.android.settings.connecteddevice.audiosharing;
+import android.bluetooth.BluetoothLeBroadcast;
+import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.content.Context;
import android.util.Log;
import android.widget.Switch;
@@ -24,36 +26,116 @@
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
+import com.android.settings.bluetooth.Utils;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.flags.Flags;
import com.android.settings.widget.SettingsMainSwitchBar;
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+import com.android.settingslib.utils.ThreadUtils;
import com.android.settingslib.widget.OnMainSwitchChangeListener;
+import java.util.ArrayList;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
public class AudioSharingSwitchBarController extends BasePreferenceController
implements DefaultLifecycleObserver, OnMainSwitchChangeListener {
private static final String TAG = "AudioSharingSwitchBarCtl";
private static final String PREF_KEY = "audio_sharing_main_switch";
-
- private final Context mContext;
private final SettingsMainSwitchBar mSwitchBar;
+ private final LocalBluetoothManager mBtManager;
+ private final LocalBluetoothLeBroadcast mBroadcast;
+ private final Executor mExecutor;
private DashboardFragment mFragment;
+ private final BluetoothLeBroadcast.Callback mBroadcastCallback =
+ new BluetoothLeBroadcast.Callback() {
+ @Override
+ public void onBroadcastStarted(int reason, int broadcastId) {
+ Log.d(
+ TAG,
+ "onBroadcastStarted(), reason = "
+ + reason
+ + ", broadcastId = "
+ + broadcastId);
+ updateSwitch();
+ }
+
+ @Override
+ public void onBroadcastStartFailed(int reason) {
+ Log.d(TAG, "onBroadcastStartFailed(), reason = " + reason);
+ // TODO: handle broadcast start fail
+ updateSwitch();
+ }
+
+ @Override
+ public void onBroadcastMetadataChanged(
+ int broadcastId, @NonNull BluetoothLeBroadcastMetadata metadata) {
+ Log.d(
+ TAG,
+ "onBroadcastMetadataChanged(), broadcastId = "
+ + broadcastId
+ + ", metadata = "
+ + metadata);
+ // TODO: handle add sink if there are connected lea devices.
+ }
+
+ @Override
+ public void onBroadcastStopped(int reason, int broadcastId) {
+ Log.d(
+ TAG,
+ "onBroadcastStopped(), reason = "
+ + reason
+ + ", broadcastId = "
+ + broadcastId);
+ updateSwitch();
+ }
+
+ @Override
+ public void onBroadcastStopFailed(int reason) {
+ Log.d(TAG, "onBroadcastStopFailed(), reason = " + reason);
+ // TODO: handle broadcast stop fail
+ updateSwitch();
+ }
+
+ @Override
+ public void onBroadcastUpdated(int reason, int broadcastId) {}
+
+ @Override
+ public void onBroadcastUpdateFailed(int reason, int broadcastId) {}
+
+ @Override
+ public void onPlaybackStarted(int reason, int broadcastId) {}
+
+ @Override
+ public void onPlaybackStopped(int reason, int broadcastId) {}
+ };
+
AudioSharingSwitchBarController(Context context, SettingsMainSwitchBar switchBar) {
super(context, PREF_KEY);
- mContext = context;
mSwitchBar = switchBar;
- mSwitchBar.setChecked(false);
+ mBtManager = Utils.getLocalBtManager(context);
+ mBroadcast = mBtManager.getProfileManager().getLeAudioBroadcastProfile();
+ mExecutor = Executors.newSingleThreadExecutor();
+ mSwitchBar.setChecked(isBroadcasting());
}
@Override
public void onStart(@NonNull LifecycleOwner owner) {
mSwitchBar.addOnSwitchChangeListener(this);
+ if (mBroadcast != null) {
+ mBroadcast.registerServiceCallBack(mExecutor, mBroadcastCallback);
+ }
}
@Override
public void onStop(@NonNull LifecycleOwner owner) {
mSwitchBar.removeOnSwitchChangeListener(this);
+ if (mBroadcast != null) {
+ mBroadcast.unregisterServiceCallBack(mBroadcastCallback);
+ }
}
@Override
@@ -63,7 +145,7 @@
if (isChecked) {
startAudioSharing();
} else {
- // TODO: stop sharing
+ stopAudioSharing();
}
}
@@ -82,10 +164,53 @@
}
private void startAudioSharing() {
- if (mFragment != null) {
- AudioSharingDialogFragment.show(mFragment);
- } else {
- Log.w(TAG, "Dialog fail to show due to null fragment.");
+ mSwitchBar.setEnabled(false);
+ if (mBroadcast == null || isBroadcasting()) {
+ Log.d(TAG, "Already in broadcasting or broadcast not support, ignore!");
+ mSwitchBar.setEnabled(true);
+ return;
}
+ if (mFragment == null) {
+ Log.w(TAG, "Dialog fail to show due to null fragment.");
+ mSwitchBar.setEnabled(true);
+ return;
+ }
+ ArrayList<String> deviceNames = new ArrayList<>();
+ AudioSharingDialogFragment.show(
+ mFragment,
+ deviceNames,
+ new AudioSharingDialogFragment.DialogEventListener() {
+ @Override
+ public void onItemClick(int position) {
+ // TODO: handle broadcast based on the dialog device item clicked
+ }
+
+ @Override
+ public void onCancelClick() {
+ mBroadcast.startBroadcast("test", /* language= */ null);
+ }
+ });
+ }
+
+ private void stopAudioSharing() {
+ mSwitchBar.setEnabled(false);
+ if (mBroadcast == null || !isBroadcasting()) {
+ Log.d(TAG, "Already not broadcasting or broadcast not support, ignore!");
+ mSwitchBar.setEnabled(true);
+ return;
+ }
+ mBroadcast.stopBroadcast(mBroadcast.getLatestBroadcastId());
+ }
+
+ private void updateSwitch() {
+ ThreadUtils.postOnMainThread(
+ () -> {
+ mSwitchBar.setChecked(isBroadcasting());
+ mSwitchBar.setEnabled(true);
+ });
+ }
+
+ private boolean isBroadcasting() {
+ return mBroadcast != null && mBroadcast.isEnabled(null);
}
}