summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorchelseahao <chelseahao@google.com>2023-12-13 17:56:41 +0800
committerchelseahao <chelseahao@google.com>2023-12-13 19:10:12 +0800
commit7edc93ec2547699d482f533bcc179398a72dc322 (patch)
tree618d77786a1c04fc542aca877d5d9817d332ae9f
parent1bb099f872d5aa1bf5858953261bae9a230cfbff (diff)
downloadSettings-7edc93ec2547699d482f533bcc179398a72dc322.tar.gz
[Audiosharing] Set visibility by active device.
Also created header and button controller for detail page. Bug: 305620450 Test: manual Change-Id: I5e468a0fb9ce49ef0fd9a0b00b51084cfd416ce0
-rw-r--r--res/xml/audio_stream_details_fragment.xml6
-rw-r--r--src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java4
-rw-r--r--src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamButtonController.java66
-rw-r--r--src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamDetailsFragment.java12
-rw-r--r--src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderController.java85
-rw-r--r--src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java25
-rw-r--r--src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java146
7 files changed, 289 insertions, 55 deletions
diff --git a/res/xml/audio_stream_details_fragment.xml b/res/xml/audio_stream_details_fragment.xml
index 97274428013..2a84939732a 100644
--- a/res/xml/audio_stream_details_fragment.xml
+++ b/res/xml/audio_stream_details_fragment.xml
@@ -25,10 +25,12 @@
android:layout="@layout/settings_entity_header"
android:selectable="false"
settings:allowDividerBelow="true"
- settings:searchable="false" />
+ settings:searchable="false"
+ settings:controller="com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamHeaderController" />
<com.android.settingslib.widget.ActionButtonsPreference
android:key="audio_stream_button"
- settings:allowDividerBelow="true" />
+ settings:allowDividerBelow="true"
+ settings:controller="com.android.settings.connecteddevice.audiosharing.audiostreams.AudioStreamButtonController" />
</PreferenceScreen>
diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java
index b5361f22057..3d4ef82a844 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingUtils.java
@@ -41,6 +41,8 @@ import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
+import javax.annotation.Nullable;
+
public class AudioSharingUtils {
private static final String TAG = "AudioSharingUtils";
private static final boolean DEBUG = BluetoothUtils.D;
@@ -237,7 +239,7 @@ public class AudioSharingUtils {
* @return An Optional containing the active LE Audio device, or an empty Optional if not found.
*/
public static Optional<CachedBluetoothDevice> getActiveSinkOnAssistant(
- LocalBluetoothManager manager) {
+ @Nullable LocalBluetoothManager manager) {
if (manager == null) {
Log.w(TAG, "getActiveSinksOnAssistant(): LocalBluetoothManager is null!");
return Optional.empty();
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamButtonController.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamButtonController.java
new file mode 100644
index 00000000000..bb729d67ec3
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamButtonController.java
@@ -0,0 +1,66 @@
+/*
+ * 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.audiostreams;
+
+import android.content.Context;
+
+import androidx.annotation.Nullable;
+import androidx.lifecycle.DefaultLifecycleObserver;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settingslib.widget.ActionButtonsPreference;
+
+public class AudioStreamButtonController extends BasePreferenceController
+ implements DefaultLifecycleObserver {
+ private static final String KEY = "audio_stream_button";
+ private @Nullable ActionButtonsPreference mPreference;
+ private int mBroadcastId = -1;
+
+ public AudioStreamButtonController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ }
+
+ @Override
+ public final void displayPreference(PreferenceScreen screen) {
+ mPreference = screen.findPreference(getPreferenceKey());
+ if (mPreference != null) {
+ mPreference.setButton1Enabled(true);
+ // TODO(chelseahao): update this based on stream connection state
+ mPreference
+ .setButton1Text(R.string.bluetooth_device_context_disconnect)
+ .setButton1Icon(R.drawable.ic_settings_close);
+ }
+ super.displayPreference(screen);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return AVAILABLE;
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return KEY;
+ }
+
+ /** Initialize with broadcast id */
+ void init(int broadcastId) {
+ mBroadcastId = broadcastId;
+ }
+}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamDetailsFragment.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamDetailsFragment.java
index 1e6982959fc..e1dc228bfe1 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamDetailsFragment.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamDetailsFragment.java
@@ -17,16 +17,28 @@
package com.android.settings.connecteddevice.audiosharing.audiostreams;
import android.content.Context;
+import android.os.Bundle;
import com.android.settings.R;
import com.android.settings.dashboard.DashboardFragment;
public class AudioStreamDetailsFragment extends DashboardFragment {
+ static final String BROADCAST_NAME_ARG = "broadcast_name";
+ static final String BROADCAST_ID_ARG = "broadcast_id";
private static final String TAG = "AudioStreamDetailsFragment";
@Override
public void onAttach(Context context) {
super.onAttach(context);
+ Bundle arguments = getArguments();
+ if (arguments != null) {
+ use(AudioStreamHeaderController.class)
+ .init(
+ this,
+ arguments.getString(BROADCAST_NAME_ARG),
+ arguments.getInt(BROADCAST_ID_ARG));
+ use(AudioStreamButtonController.class).init(arguments.getInt(BROADCAST_ID_ARG));
+ }
}
@Override
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderController.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderController.java
new file mode 100644
index 00000000000..89f24bccdbd
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderController.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.audiostreams;
+
+import android.content.Context;
+
+import androidx.lifecycle.DefaultLifecycleObserver;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.core.BasePreferenceController;
+import com.android.settings.dashboard.DashboardFragment;
+import com.android.settings.widget.EntityHeaderController;
+import com.android.settingslib.widget.LayoutPreference;
+
+import javax.annotation.Nullable;
+
+public class AudioStreamHeaderController extends BasePreferenceController
+ implements DefaultLifecycleObserver {
+ private static final String KEY = "audio_stream_header";
+ private @Nullable EntityHeaderController mHeaderController;
+ private @Nullable DashboardFragment mFragment;
+ private String mBroadcastName = "";
+ private int mBroadcastId = -1;
+
+ public AudioStreamHeaderController(Context context, String preferenceKey) {
+ super(context, preferenceKey);
+ }
+
+ @Override
+ public final void displayPreference(PreferenceScreen screen) {
+ LayoutPreference headerPreference = screen.findPreference(KEY);
+ if (headerPreference != null && mFragment != null) {
+ mHeaderController =
+ EntityHeaderController.newInstance(
+ mFragment.getActivity(),
+ mFragment,
+ headerPreference.findViewById(R.id.entity_header));
+ if (mBroadcastName != null) {
+ mHeaderController.setLabel(mBroadcastName);
+ }
+ mHeaderController.setIcon(
+ screen.getContext().getDrawable(R.drawable.ic_bt_audio_sharing));
+ // TODO(chelseahao): update this based on stream connection state
+ mHeaderController.setSummary("Listening now");
+ mHeaderController.done(true);
+ screen.addPreference(headerPreference);
+ }
+ super.displayPreference(screen);
+ }
+
+ @Override
+ public int getAvailabilityStatus() {
+ return AVAILABLE;
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return KEY;
+ }
+
+ /** Initialize with {@link AudioStreamDetailsFragment} and broadcast name and id */
+ void init(
+ AudioStreamDetailsFragment audioStreamDetailsFragment,
+ String broadcastName,
+ int broadcastId) {
+ mFragment = audioStreamDetailsFragment;
+ mBroadcastName = broadcastName;
+ mBroadcastId = broadcastId;
+ }
+}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java
index 5acbc1f7b93..198e8e5f335 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsHelper.java
@@ -80,8 +80,8 @@ class AudioStreamsHelper {
});
}
- /** Removes all sources from LE broadcasts associated for all active sinks. */
- void removeSource() {
+ /** Removes sources from LE broadcasts associated for all active sinks based on broadcast Id. */
+ void removeSource(int broadcastId) {
if (mLeBroadcastAssistant == null) {
Log.w(TAG, "removeSource(): LeBroadcastAssistant is null!");
return;
@@ -93,14 +93,17 @@ class AudioStreamsHelper {
if (DEBUG) {
Log.d(
TAG,
- "removeSource(): remove all sources from sink : "
+ "removeSource(): remove all sources with broadcast id :"
+ + broadcastId
+ + " from sink : "
+ sink.getAddress());
}
- var sources = mLeBroadcastAssistant.getAllSources(sink);
- if (!sources.isEmpty()) {
- mLeBroadcastAssistant.removeSource(
- sink, sources.get(0).getSourceId());
- }
+ mLeBroadcastAssistant.getAllSources(sink).stream()
+ .filter(state -> state.getBroadcastId() == broadcastId)
+ .forEach(
+ state ->
+ mLeBroadcastAssistant.removeSource(
+ sink, state.getSourceId()));
}
});
}
@@ -121,6 +124,12 @@ class AudioStreamsHelper {
return mLeBroadcastAssistant;
}
+ static boolean isConnected(BluetoothLeBroadcastReceiveState state) {
+ return state.getPaSyncState() == BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED
+ && state.getBigEncryptionState()
+ == BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING;
+ }
+
private static List<BluetoothDevice> getActiveSinksOnAssistant(
@Nullable LocalBluetoothManager manager) {
if (manager == null) {
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java
index 45f0c2fda3e..3c005b294b5 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java
@@ -22,9 +22,9 @@ import android.app.AlertDialog;
import android.app.settings.SettingsEnums;
import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.bluetooth.BluetoothLeBroadcastReceiveState;
+import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.os.Bundle;
-import android.provider.Settings;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -42,8 +42,11 @@ import com.android.settings.bluetooth.Utils;
import com.android.settings.connecteddevice.audiosharing.AudioSharingUtils;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.core.SubSettingLauncher;
+import com.android.settingslib.bluetooth.BluetoothCallback;
import com.android.settingslib.bluetooth.BluetoothUtils;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.utils.ThreadUtils;
import java.nio.charset.StandardCharsets;
@@ -57,11 +60,22 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
implements DefaultLifecycleObserver {
private static final String TAG = "AudioStreamsProgressCategoryController";
private static final boolean DEBUG = BluetoothUtils.D;
+ private final BluetoothCallback mBluetoothCallback =
+ new BluetoothCallback() {
+ @Override
+ public void onActiveDeviceChanged(
+ @Nullable CachedBluetoothDevice activeDevice, int bluetoothProfile) {
+ if (bluetoothProfile == BluetoothProfile.LE_AUDIO) {
+ mExecutor.execute(() -> init(activeDevice != null));
+ }
+ }
+ };
private final Executor mExecutor;
private final AudioStreamsBroadcastAssistantCallback mBroadcastAssistantCallback;
private final AudioStreamsHelper mAudioStreamsHelper;
private final @Nullable LocalBluetoothLeBroadcastAssistant mLeBroadcastAssistant;
+ private final @Nullable LocalBluetoothManager mBluetoothManager;
private final ConcurrentHashMap<Integer, AudioStreamPreference> mBroadcastIdToPreferenceMap =
new ConcurrentHashMap<>();
private AudioStreamsProgressCategoryPreference mCategoryPreference;
@@ -69,7 +83,8 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
public AudioStreamsProgressCategoryController(Context context, String preferenceKey) {
super(context, preferenceKey);
mExecutor = Executors.newSingleThreadExecutor();
- mAudioStreamsHelper = new AudioStreamsHelper(Utils.getLocalBtManager(mContext));
+ mBluetoothManager = Utils.getLocalBtManager(mContext);
+ mAudioStreamsHelper = new AudioStreamsHelper(mBluetoothManager);
mLeBroadcastAssistant = mAudioStreamsHelper.getLeBroadcastAssistant();
mBroadcastAssistantCallback = new AudioStreamsBroadcastAssistantCallback(this);
}
@@ -87,48 +102,24 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
@Override
public void onStart(@NonNull LifecycleOwner owner) {
- if (mLeBroadcastAssistant == null) {
- Log.w(TAG, "onStart(): LeBroadcastAssistant is null!");
- return;
- }
- mBroadcastIdToPreferenceMap.clear();
- if (mCategoryPreference != null) {
- mCategoryPreference.removeAll();
+ if (mBluetoothManager != null) {
+ mBluetoothManager.getEventManager().registerCallback(mBluetoothCallback);
}
mExecutor.execute(
() -> {
- mLeBroadcastAssistant.registerServiceCallBack(
- mExecutor, mBroadcastAssistantCallback);
- if (DEBUG) {
- Log.d(TAG, "scanAudioStreamsStart()");
- }
- mLeBroadcastAssistant.startSearchingForSources(emptyList());
- // Display currently connected streams
- var unused =
- ThreadUtils.postOnBackgroundThread(
- () ->
- mAudioStreamsHelper
- .getAllSources()
- .forEach(this::handleSourceConnected));
+ boolean hasActive =
+ AudioSharingUtils.getActiveSinkOnAssistant(mBluetoothManager)
+ .isPresent();
+ init(hasActive);
});
}
@Override
public void onStop(@NonNull LifecycleOwner owner) {
- if (mLeBroadcastAssistant == null) {
- Log.w(TAG, "onStop(): LeBroadcastAssistant is null!");
- return;
+ if (mBluetoothManager != null) {
+ mBluetoothManager.getEventManager().unregisterCallback(mBluetoothCallback);
}
- mExecutor.execute(
- () -> {
- if (mLeBroadcastAssistant.isSearchInProgress()) {
- if (DEBUG) {
- Log.d(TAG, "scanAudioStreamsStop()");
- }
- mLeBroadcastAssistant.stopSearchingForSources();
- }
- mLeBroadcastAssistant.unregisterServiceCallBack(mBroadcastAssistantCallback);
- });
+ mExecutor.execute(this::stopScanning);
}
void setScanning(boolean isScanning) {
@@ -142,7 +133,10 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
Preference.OnPreferenceClickListener addSourceOrShowDialog =
preference -> {
if (DEBUG) {
- Log.d(TAG, "preferenceClicked(): attempt to join broadcast");
+ Log.d(
+ TAG,
+ "preferenceClicked(): attempt to join broadcast id : "
+ + source.getBroadcastId());
}
if (source.isEncrypted()) {
ThreadUtils.postOnMainThread(
@@ -177,11 +171,13 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
}
});
}
- mAudioStreamsHelper.removeSource();
+ mAudioStreamsHelper.removeSource(broadcastId);
}
void handleSourceConnected(BluetoothLeBroadcastReceiveState state) {
- // TODO(chelseahao): only continue when the state indicates a successful connection
+ if (!AudioStreamsHelper.isConnected(state)) {
+ return;
+ }
mBroadcastIdToPreferenceMap.compute(
state.getBroadcastId(),
(k, v) -> {
@@ -194,7 +190,7 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
ThreadUtils.postOnMainThread(
() -> {
preference.setIsConnected(
- true, p -> launchDetailFragment((AudioStreamPreference) p));
+ true, p -> launchDetailFragment(state.getBroadcastId()));
if (mCategoryPreference != null && !existed) {
mCategoryPreference.addPreference(preference);
}
@@ -208,11 +204,73 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
AudioSharingUtils.toastMessage(mContext, msg);
}
- private boolean launchDetailFragment(AudioStreamPreference preference) {
+ private void init(boolean hasActive) {
+ mBroadcastIdToPreferenceMap.clear();
+ ThreadUtils.postOnMainThread(
+ () -> {
+ if (mCategoryPreference != null) {
+ mCategoryPreference.removeAll();
+ mCategoryPreference.setVisible(hasActive);
+ }
+ });
+ if (hasActive) {
+ startScanning();
+ } else {
+ stopScanning();
+ }
+ }
+
+ private void startScanning() {
+ if (mLeBroadcastAssistant == null) {
+ Log.w(TAG, "startScanning(): LeBroadcastAssistant is null!");
+ return;
+ }
+ if (mLeBroadcastAssistant.isSearchInProgress()) {
+ showToast("Failed to start scanning, please try again.");
+ return;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "startScanning()");
+ }
+ mLeBroadcastAssistant.registerServiceCallBack(mExecutor, mBroadcastAssistantCallback);
+ mLeBroadcastAssistant.startSearchingForSources(emptyList());
+
+ // Display currently connected streams
+ var unused =
+ ThreadUtils.postOnBackgroundThread(
+ () ->
+ mAudioStreamsHelper
+ .getAllSources()
+ .forEach(this::handleSourceConnected));
+ }
+
+ private void stopScanning() {
+ if (mLeBroadcastAssistant == null) {
+ Log.w(TAG, "stopScanning(): LeBroadcastAssistant is null!");
+ return;
+ }
+ if (mLeBroadcastAssistant.isSearchInProgress()) {
+ if (DEBUG) {
+ Log.d(TAG, "stopScanning()");
+ }
+ mLeBroadcastAssistant.stopSearchingForSources();
+ }
+ mLeBroadcastAssistant.unregisterServiceCallBack(mBroadcastAssistantCallback);
+ }
+
+ private boolean launchDetailFragment(int broadcastId) {
+ if (!mBroadcastIdToPreferenceMap.containsKey(broadcastId)) {
+ Log.w(
+ TAG,
+ "launchDetailFragment(): broadcastId not exist in BroadcastIdToPreferenceMap!");
+ return false;
+ }
+ AudioStreamPreference preference = mBroadcastIdToPreferenceMap.get(broadcastId);
+
Bundle broadcast = new Bundle();
broadcast.putString(
- Settings.Secure.BLUETOOTH_LE_BROADCAST_PROGRAM_INFO,
- (String) preference.getTitle());
+ AudioStreamDetailsFragment.BROADCAST_NAME_ARG, (String) preference.getTitle());
+ broadcast.putInt(AudioStreamDetailsFragment.BROADCAST_ID_ARG, broadcastId);
new SubSettingLauncher(mContext)
.setTitleText("Audio stream details")
@@ -240,8 +298,8 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
(dialog, which) -> {
var code =
((EditText)
- layout.requireViewById(
- R.id.broadcast_edit_text))
+ layout.requireViewById(
+ R.id.broadcast_edit_text))
.getText()
.toString();
mAudioStreamsHelper.addSource(