From 2f2afa22fd02110766d8dfc060bffe8f76616f06 Mon Sep 17 00:00:00 2001 From: Yiyi Shen Date: Tue, 7 Nov 2023 18:15:00 +0800 Subject: [PATCH] [Audiosharing] Add audio sharing section to Connected devices page. The section will show up during a sharing session. Flagged with enable_le_audio_sharing Bug: 305620450 Test: Manual Change-Id: I59cf81b35dcbf328b253d72d7fdc86af450251ee --- res/xml/connected_devices.xml | 13 + .../ConnectedDeviceDashboardFragment.java | 47 ++-- .../AudioSharingBluetoothDeviceUpdater.java | 112 ++++++++ ...udioSharingDevicePreferenceController.java | 260 ++++++++++++++++++ .../ConnectedDeviceDashboardFragmentTest.java | 17 +- 5 files changed, 428 insertions(+), 21 deletions(-) create mode 100644 src/com/android/settings/connecteddevice/audiosharing/AudioSharingBluetoothDeviceUpdater.java create mode 100644 src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceController.java diff --git a/res/xml/connected_devices.xml b/res/xml/connected_devices.xml index 0043a2d12b0..1d2dd24e26c 100644 --- a/res/xml/connected_devices.xml +++ b/res/xml/connected_devices.xml @@ -26,6 +26,19 @@ settings:controller="com.android.settings.slices.SlicePreferenceController" settings:allowDividerBelow="true"/> + + + + sourceList = + assistant.getAllSources(cachedDevice.getDevice()); + if (!sourceList.isEmpty()) return true; + // Return true if member device is in broadcast. + for (CachedBluetoothDevice device : cachedDevice.getMemberDevice()) { + List list = + assistant.getAllSources(device.getDevice()); + if (!list.isEmpty()) return true; + } + return false; + } + + @Override + protected String getPreferenceKey() { + return PREF_KEY; + } + + @Override + protected String getLogTag() { + return TAG; + } + + @Override + protected void update(CachedBluetoothDevice cachedBluetoothDevice) { + super.update(cachedBluetoothDevice); + Log.d(TAG, "Map : " + mPreferenceMap); + } +} diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceController.java new file mode 100644 index 00000000000..d4803c63699 --- /dev/null +++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDevicePreferenceController.java @@ -0,0 +1,260 @@ +/* + * 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 android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothLeBroadcastAssistant; +import android.bluetooth.BluetoothLeBroadcastMetadata; +import android.bluetooth.BluetoothLeBroadcastReceiveState; +import android.content.Context; +import android.content.pm.PackageManager; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.lifecycle.DefaultLifecycleObserver; +import androidx.lifecycle.LifecycleOwner; +import androidx.preference.Preference; +import androidx.preference.PreferenceGroup; +import androidx.preference.PreferenceScreen; + +import com.android.settings.bluetooth.BluetoothDeviceUpdater; +import com.android.settings.bluetooth.Utils; +import com.android.settings.connecteddevice.DevicePreferenceCallback; +import com.android.settings.core.BasePreferenceController; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.flags.Flags; +import com.android.settingslib.bluetooth.BluetoothCallback; +import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant; +import com.android.settingslib.bluetooth.LocalBluetoothManager; + +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + +public class AudioSharingDevicePreferenceController extends BasePreferenceController + implements DefaultLifecycleObserver, DevicePreferenceCallback, BluetoothCallback { + + private static final String TAG = "AudioSharingDevicePrefController"; + private static final String KEY = "audio_sharing_device_list"; + private static final String KEY_AUDIO_SHARING_SETTINGS = + "connected_device_audio_sharing_settings"; + + private final LocalBluetoothManager mLocalBtManager; + private final LocalBluetoothLeBroadcastAssistant mAssistant; + private final Executor mExecutor; + private PreferenceGroup mPreferenceGroup; + private Preference mAudioSharingSettingsPreference; + private BluetoothDeviceUpdater mBluetoothDeviceUpdater; + + private BluetoothLeBroadcastAssistant.Callback mBroadcastAssistantCallback = + new BluetoothLeBroadcastAssistant.Callback() { + @Override + public void onSearchStarted(int reason) {} + + @Override + public void onSearchStartFailed(int reason) {} + + @Override + public void onSearchStopped(int reason) {} + + @Override + public void onSearchStopFailed(int reason) {} + + @Override + public void onSourceFound(@NonNull BluetoothLeBroadcastMetadata source) {} + + @Override + public void onSourceAdded(@NonNull BluetoothDevice sink, int sourceId, int reason) { + Log.d( + TAG, + "onSourceAdded(), sink = " + + sink + + ", sourceId = " + + sourceId + + ", reason = " + + reason); + mBluetoothDeviceUpdater.forceUpdate(); + } + + @Override + public void onSourceAddFailed( + @NonNull BluetoothDevice sink, + @NonNull BluetoothLeBroadcastMetadata source, + int reason) { + Log.d( + TAG, + "onSourceAddFailed(), sink = " + + sink + + ", source = " + + source + + ", reason = " + + reason); + } + + @Override + public void onSourceModified( + @NonNull BluetoothDevice sink, int sourceId, int reason) {} + + @Override + public void onSourceModifyFailed( + @NonNull BluetoothDevice sink, int sourceId, int reason) {} + + @Override + public void onSourceRemoved( + @NonNull BluetoothDevice sink, int sourceId, int reason) { + Log.d( + TAG, + "onSourceRemoved(), sink = " + + sink + + ", sourceId = " + + sourceId + + ", reason = " + + reason); + mBluetoothDeviceUpdater.forceUpdate(); + } + + @Override + public void onSourceRemoveFailed( + @NonNull BluetoothDevice sink, int sourceId, int reason) { + Log.d( + TAG, + "onSourceRemoveFailed(), sink = " + + sink + + ", sourceId = " + + sourceId + + ", reason = " + + reason); + } + + @Override + public void onReceiveStateChanged( + BluetoothDevice sink, + int sourceId, + BluetoothLeBroadcastReceiveState state) {} + }; + + public AudioSharingDevicePreferenceController(Context context) { + super(context, KEY); + mLocalBtManager = Utils.getLocalBtManager(mContext); + mAssistant = mLocalBtManager.getProfileManager().getLeAudioBroadcastAssistantProfile(); + mExecutor = Executors.newSingleThreadExecutor(); + } + + @Override + public void onStart(@NonNull LifecycleOwner owner) { + if (mLocalBtManager == null) { + Log.e(TAG, "onStart() Bluetooth is not supported on this device"); + return; + } + if (mAssistant == null) { + Log.e(TAG, "onStart() Broadcast assistant is not supported on this device"); + return; + } + if (mBluetoothDeviceUpdater == null) { + Log.e(TAG, "onStart() Bluetooth device updater is not initialized"); + return; + } + mLocalBtManager.getEventManager().registerCallback(this); + mAssistant.registerServiceCallBack(mExecutor, mBroadcastAssistantCallback); + mBluetoothDeviceUpdater.registerCallback(); + mBluetoothDeviceUpdater.refreshPreference(); + } + + @Override + public void onStop(@NonNull LifecycleOwner owner) { + if (mLocalBtManager == null) { + Log.e(TAG, "onStop() Bluetooth is not supported on this device"); + return; + } + if (mAssistant == null) { + Log.e(TAG, "onStop() Broadcast assistant is not supported on this device"); + return; + } + if (mBluetoothDeviceUpdater == null) { + Log.e(TAG, "onStop() Bluetooth device updater is not initialized"); + return; + } + mLocalBtManager.getEventManager().unregisterCallback(this); + // TODO: verify the reason for failing to unregister + try { + mAssistant.unregisterServiceCallBack(mBroadcastAssistantCallback); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Fail to unregister assistant callback due to " + e.getMessage()); + } + mBluetoothDeviceUpdater.unregisterCallback(); + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + + mPreferenceGroup = screen.findPreference(KEY); + mAudioSharingSettingsPreference = + mPreferenceGroup.findPreference(KEY_AUDIO_SHARING_SETTINGS); + mPreferenceGroup.setVisible(false); + mAudioSharingSettingsPreference.setVisible(false); + + if (isAvailable()) { + mBluetoothDeviceUpdater.setPrefContext(screen.getContext()); + mBluetoothDeviceUpdater.forceUpdate(); + } + } + + @Override + public int getAvailabilityStatus() { + return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH) + && Flags.enableLeAudioSharing() + ? AVAILABLE_UNSEARCHABLE + : UNSUPPORTED_ON_DEVICE; + } + + @Override + public String getPreferenceKey() { + return KEY; + } + + @Override + public void onDeviceAdded(Preference preference) { + if (mPreferenceGroup.getPreferenceCount() == 1) { + mPreferenceGroup.setVisible(true); + mAudioSharingSettingsPreference.setVisible(true); + } + mPreferenceGroup.addPreference(preference); + } + + @Override + public void onDeviceRemoved(Preference preference) { + mPreferenceGroup.removePreference(preference); + if (mPreferenceGroup.getPreferenceCount() == 1) { + mPreferenceGroup.setVisible(false); + mAudioSharingSettingsPreference.setVisible(false); + } + } + + /** + * Initialize the controller. + * + * @param fragment The fragment to provide the context and metrics category for {@link + * AudioSharingBluetoothDeviceUpdater}. + */ + public void init(DashboardFragment fragment) { + mBluetoothDeviceUpdater = + new AudioSharingBluetoothDeviceUpdater( + fragment.getContext(), + AudioSharingDevicePreferenceController.this, + fragment.getMetricsCategory()); + } +} diff --git a/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragmentTest.java b/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragmentTest.java index d381975352e..1d1f2f8fc18 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragmentTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/ConnectedDeviceDashboardFragmentTest.java @@ -56,6 +56,9 @@ public class ConnectedDeviceDashboardFragmentTest { private static final String KEY_FAST_PAIR_DEVICE_SEE_ALL = "fast_pair_devices_see_all"; private static final String KEY_FAST_PAIR_DEVICE_LIST = "fast_pair_devices"; private static final String KEY_ADD_BT_DEVICES = "add_bt_devices"; + private static final String KEY_AUDIO_SHARING_DEVICE_LIST = "audio_sharing_device_list"; + private static final String KEY_AUDIO_SHARING_SETTINGS = + "connected_device_audio_sharing_settings"; private static final String SETTINGS_PACKAGE_NAME = "com.android.settings"; private static final String SYSTEMUI_PACKAGE_NAME = "com.android.systemui"; private static final String SLICE_ACTION = "com.android.settings.SEARCH_RESULT_TRAMPOLINE"; @@ -93,9 +96,17 @@ public class ConnectedDeviceDashboardFragmentTest { final List niks = ConnectedDeviceDashboardFragment.SEARCH_INDEX_DATA_PROVIDER .getNonIndexableKeys(mContext); - assertThat(niks).containsExactly(KEY_CONNECTED_DEVICES, KEY_AVAILABLE_DEVICES, - KEY_NEARBY_DEVICES, KEY_DISCOVERABLE_FOOTER, KEY_SAVED_DEVICE_SEE_ALL, - KEY_FAST_PAIR_DEVICE_SEE_ALL, KEY_FAST_PAIR_DEVICE_LIST); + assertThat(niks) + .containsExactly( + KEY_CONNECTED_DEVICES, + KEY_AVAILABLE_DEVICES, + KEY_NEARBY_DEVICES, + KEY_DISCOVERABLE_FOOTER, + KEY_SAVED_DEVICE_SEE_ALL, + KEY_FAST_PAIR_DEVICE_SEE_ALL, + KEY_FAST_PAIR_DEVICE_LIST, + KEY_AUDIO_SHARING_DEVICE_LIST, + KEY_AUDIO_SHARING_SETTINGS); } @Test