From d1f0efc282bfead48eef35cf2b28a9aaec537643 Mon Sep 17 00:00:00 2001 From: Yiyi Shen Date: Thu, 16 Nov 2023 14:00:11 +0800 Subject: [PATCH] [Audiosharing] Impl active device switch for calls and alarms Flagged with enable_le_audio_sharing Bug: 305620450 Test: Manual Change-Id: Id1e417cd1ae48bbb4ddebb4b30e2001e18bef7ee --- .../audiosharing/AudioSharingDeviceItem.java | 10 ++- .../audiosharing/AudioSharingUtils.java | 5 +- .../CallsAndAlarmsDialogFragment.java | 41 ++++++++-- .../CallsAndAlarmsPreferenceController.java | 80 +++++++++++++++++-- 4 files changed, 124 insertions(+), 12 deletions(-) diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceItem.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceItem.java index a68117a17cd..5998e30c5bc 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceItem.java +++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceItem.java @@ -22,10 +22,12 @@ import android.os.Parcelable; public final class AudioSharingDeviceItem implements Parcelable { private final String mName; private final int mGroupId; + private final boolean mIsActive; - public AudioSharingDeviceItem(String name, int groupId) { + public AudioSharingDeviceItem(String name, int groupId, boolean isActive) { mName = name; mGroupId = groupId; + mIsActive = isActive; } public String getName() { @@ -36,15 +38,21 @@ public final class AudioSharingDeviceItem implements Parcelable { return mGroupId; } + public boolean isActive() { + return mIsActive; + } + public AudioSharingDeviceItem(Parcel in) { mName = in.readString(); mGroupId = in.readInt(); + mIsActive = in.readBoolean(); } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(mName); dest.writeInt(mGroupId); + dest.writeBoolean(mIsActive); } @Override diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java index 4ece70e00fb..a0d44ff18b9 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java +++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java @@ -120,6 +120,9 @@ public class AudioSharingUtils { /** Build {@link AudioSharingDeviceItem} from {@link CachedBluetoothDevice}. */ public static AudioSharingDeviceItem buildAudioSharingDeviceItem( CachedBluetoothDevice cachedDevice) { - return new AudioSharingDeviceItem(cachedDevice.getName(), cachedDevice.getGroupId()); + return new AudioSharingDeviceItem( + cachedDevice.getName(), + cachedDevice.getGroupId(), + BluetoothUtils.isActiveLeAudioDevice(cachedDevice)); } } diff --git a/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsDialogFragment.java b/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsDialogFragment.java index 0577f70ec5a..47f70c78f90 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsDialogFragment.java +++ b/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsDialogFragment.java @@ -28,9 +28,25 @@ import com.android.settings.R; import com.android.settings.core.instrumentation.InstrumentedDialogFragment; import com.android.settings.flags.Flags; +import java.util.ArrayList; + /** Provides a dialog to choose the active device for calls and alarms. */ public class CallsAndAlarmsDialogFragment extends InstrumentedDialogFragment { private static final String TAG = "CallsAndAlarmsDialog"; + private static final String BUNDLE_KEY_DEVICE_ITEMS = "bundle_key_device_items"; + + // 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 to set active for calls and alarms in the dialog. + * + * @param item The device item clicked. + */ + void onItemClick(AudioSharingDeviceItem item); + } + + private static DialogEventListener sListener; @Override public int getMetricsCategory() { @@ -41,28 +57,43 @@ public class CallsAndAlarmsDialogFragment extends InstrumentedDialogFragment { * Display the {@link CallsAndAlarmsDialogFragment} dialog. * * @param host The Fragment this dialog will be hosted. + * @param deviceItems The connected device items in audio sharing session. + * @param listener The callback to handle the user action on this dialog. */ - public static void show(Fragment host) { + public static void show( + Fragment host, + ArrayList deviceItems, + DialogEventListener listener) { if (!Flags.enableLeAudioSharing()) return; final FragmentManager manager = host.getChildFragmentManager(); + sListener = listener; if (manager.findFragmentByTag(TAG) == null) { + final Bundle bundle = new Bundle(); + bundle.putParcelableArrayList(BUNDLE_KEY_DEVICE_ITEMS, deviceItems); final CallsAndAlarmsDialogFragment dialog = new CallsAndAlarmsDialogFragment(); + dialog.setArguments(bundle); dialog.show(manager, TAG); } } @Override public Dialog onCreateDialog(Bundle savedInstanceState) { - // TODO: use real device names - String[] choices = {"Buds 1", "Buds 2"}; + Bundle arguments = requireArguments(); + ArrayList deviceItems = + arguments.getParcelableArrayList(BUNDLE_KEY_DEVICE_ITEMS); + int checkedItem = -1; + // deviceItems is ordered. The active device is put in the first place if it does exist + if (!deviceItems.isEmpty() && deviceItems.get(0).isActive()) checkedItem = 0; + String[] choices = + deviceItems.stream().map(AudioSharingDeviceItem::getName).toArray(String[]::new); AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()) .setTitle(R.string.calls_and_alarms_device_title) .setSingleChoiceItems( choices, - 0, // TODO: set to current active device. + checkedItem, (dialog, which) -> { - // TODO: set device to active device for calls and alarms. + sListener.onItemClick(deviceItems.get(which)); }); return builder.create(); } diff --git a/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsPreferenceController.java b/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsPreferenceController.java index 44e75ecda22..a7d18e76415 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsPreferenceController.java +++ b/src/com/android/settings/connecteddevice/audiosharing/CallsAndAlarmsPreferenceController.java @@ -16,23 +16,42 @@ package com.android.settings.connecteddevice.audiosharing; +import android.annotation.Nullable; +import android.bluetooth.BluetoothProfile; import android.content.Context; import android.util.Log; +import androidx.annotation.NonNull; +import androidx.lifecycle.DefaultLifecycleObserver; +import androidx.lifecycle.LifecycleOwner; import androidx.preference.PreferenceScreen; +import com.android.settings.bluetooth.Utils; 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 java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; /** PreferenceController to control the dialog to choose the active device for calls and alarms */ -public class CallsAndAlarmsPreferenceController extends AudioSharingBasePreferenceController { +public class CallsAndAlarmsPreferenceController extends AudioSharingBasePreferenceController + implements BluetoothCallback, DefaultLifecycleObserver { private static final String TAG = "CallsAndAlarmsPreferenceController"; - private static final String PREF_KEY = "calls_and_alarms"; + + private final LocalBluetoothManager mLocalBtManager; private DashboardFragment mFragment; + Map> mGroupedConnectedDevices = new HashMap<>(); + private ArrayList mDeviceItemsInSharingSession = new ArrayList<>(); public CallsAndAlarmsPreferenceController(Context context) { super(context, PREF_KEY); + mLocalBtManager = Utils.getLocalBtManager(mContext); } @Override @@ -43,17 +62,60 @@ public class CallsAndAlarmsPreferenceController extends AudioSharingBasePreferen @Override public void displayPreference(PreferenceScreen screen) { super.displayPreference(screen); + 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(""); + } mPreference.setOnPreferenceClickListener( preference -> { - if (mFragment != null) { - CallsAndAlarmsDialogFragment.show(mFragment); - } else { + if (mFragment == null) { Log.w(TAG, "Dialog fail to show due to null host."); + return true; + } + updateDeviceItemsInSharingSession(); + if (mDeviceItemsInSharingSession.size() >= 2) { + CallsAndAlarmsDialogFragment.show( + mFragment, + mDeviceItemsInSharingSession, + (AudioSharingDeviceItem item) -> { + for (CachedBluetoothDevice device : + mGroupedConnectedDevices.get(item.getGroupId())) { + device.setActive(); + } + }); } return true; }); } + @Override + public void onStart(@NonNull LifecycleOwner owner) { + if (mLocalBtManager != null) { + mLocalBtManager.getEventManager().registerCallback(this); + } + } + + @Override + public void onStop(@NonNull LifecycleOwner owner) { + if (mLocalBtManager != null) { + mLocalBtManager.getEventManager().unregisterCallback(this); + } + } + + @Override + public void onActiveDeviceChanged( + @Nullable CachedBluetoothDevice activeDevice, int bluetoothProfile) { + if (bluetoothProfile != BluetoothProfile.LE_AUDIO) { + Log.d(TAG, "Ignore onActiveDeviceChanged, not LE_AUDIO profile"); + return; + } + mPreference.setSummary(activeDevice == null ? "" : activeDevice.getName()); + } + /** * Initialize the controller. * @@ -62,4 +124,12 @@ public class CallsAndAlarmsPreferenceController extends AudioSharingBasePreferen public void init(DashboardFragment fragment) { this.mFragment = fragment; } + + private void updateDeviceItemsInSharingSession() { + mGroupedConnectedDevices = + AudioSharingUtils.fetchConnectedDevicesByGroupId(mLocalBtManager); + mDeviceItemsInSharingSession = + AudioSharingUtils.buildOrderedDeviceItemsInSharingSession( + mGroupedConnectedDevices, mLocalBtManager); + } }