diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragment.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragment.java index 5b9990755b8..1fd0b878602 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragment.java +++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDialogFragment.java @@ -38,6 +38,26 @@ import java.util.ArrayList; 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 @@ public class AudioSharingDialogFragment extends InstrumentedDialogFragment { * * @param host The Fragment this dialog will be hosted. */ - public static void show(Fragment host) { + public static void show( + Fragment host, ArrayList 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 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 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 a375a3c10e4..bd8027c7bd8 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.annotation.NonNull; 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 @@ public class AudioSharingSwitchBarController extends BasePreferenceController if (isChecked) { startAudioSharing(); } else { - // TODO: stop sharing + stopAudioSharing(); } } @@ -82,10 +164,53 @@ public class AudioSharingSwitchBarController extends BasePreferenceController } 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 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); } }