[Audiosharing] Add button action in detail page.
Bug: 308368124
Test: manual
Change-Id: I44e631cb75af432965d2221e71676146ea1537b6
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamButtonController.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamButtonController.java
index bb729d6..47597cf 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamButtonController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamButtonController.java
@@ -16,39 +16,170 @@
package com.android.settings.connecteddevice.audiosharing.audiostreams;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeBroadcastAssistant;
+import android.bluetooth.BluetoothLeBroadcastMetadata;
+import android.bluetooth.BluetoothLeBroadcastReceiveState;
import android.content.Context;
+import android.util.Log;
+import android.view.View;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.DefaultLifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
+import com.android.settings.bluetooth.Utils;
import com.android.settings.core.BasePreferenceController;
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
+import com.android.settingslib.utils.ThreadUtils;
import com.android.settingslib.widget.ActionButtonsPreference;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
public class AudioStreamButtonController extends BasePreferenceController
implements DefaultLifecycleObserver {
+ private static final String TAG = "AudioStreamButtonController";
private static final String KEY = "audio_stream_button";
+ private final BluetoothLeBroadcastAssistant.Callback mBroadcastAssistantCallback =
+ new AudioStreamsBroadcastAssistantCallback() {
+ @Override
+ public void onSourceRemoved(BluetoothDevice sink, int sourceId, int reason) {
+ super.onSourceRemoved(sink, sourceId, reason);
+ updateButton();
+ }
+
+ @Override
+ public void onSourceRemoveFailed(BluetoothDevice sink, int sourceId, int reason) {
+ super.onSourceRemoveFailed(sink, sourceId, reason);
+ updateButton();
+ }
+
+ @Override
+ public void onReceiveStateChanged(
+ BluetoothDevice sink,
+ int sourceId,
+ BluetoothLeBroadcastReceiveState state) {
+ super.onReceiveStateChanged(sink, sourceId, state);
+ if (mAudioStreamsHelper.isConnected(state)) {
+ updateButton();
+ }
+ }
+
+ @Override
+ public void onSourceAddFailed(
+ BluetoothDevice sink, BluetoothLeBroadcastMetadata source, int reason) {
+ super.onSourceAddFailed(sink, source, reason);
+ updateButton();
+ }
+
+ @Override
+ public void onSourceLost(int broadcastId) {
+ super.onSourceLost(broadcastId);
+ updateButton();
+ }
+ };
+
+ private final AudioStreamsRepository mAudioStreamsRepository =
+ AudioStreamsRepository.getInstance();
+ private final Executor mExecutor;
+ private final AudioStreamsHelper mAudioStreamsHelper;
+ private final @Nullable LocalBluetoothLeBroadcastAssistant mLeBroadcastAssistant;
private @Nullable ActionButtonsPreference mPreference;
private int mBroadcastId = -1;
public AudioStreamButtonController(Context context, String preferenceKey) {
super(context, preferenceKey);
+ mExecutor = Executors.newSingleThreadExecutor();
+ mAudioStreamsHelper = new AudioStreamsHelper(Utils.getLocalBtManager(context));
+ mLeBroadcastAssistant = mAudioStreamsHelper.getLeBroadcastAssistant();
+ }
+
+ @Override
+ public void onStart(@NonNull LifecycleOwner owner) {
+ if (mLeBroadcastAssistant == null) {
+ Log.w(TAG, "onStart(): LeBroadcastAssistant is null!");
+ return;
+ }
+ mLeBroadcastAssistant.registerServiceCallBack(mExecutor, mBroadcastAssistantCallback);
+ }
+
+ @Override
+ public void onStop(@NonNull LifecycleOwner owner) {
+ if (mLeBroadcastAssistant == null) {
+ Log.w(TAG, "onStop(): LeBroadcastAssistant is null!");
+ return;
+ }
+ mLeBroadcastAssistant.unregisterServiceCallBack(mBroadcastAssistantCallback);
}
@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);
- }
+ updateButton();
super.displayPreference(screen);
}
+ private void updateButton() {
+ if (mPreference != null) {
+ if (mAudioStreamsHelper.getAllConnectedSources().stream()
+ .map(BluetoothLeBroadcastReceiveState::getBroadcastId)
+ .anyMatch(connectedBroadcastId -> connectedBroadcastId == mBroadcastId)) {
+ ThreadUtils.postOnMainThread(
+ () -> {
+ if (mPreference != null) {
+ mPreference.setButton1Enabled(true);
+ mPreference
+ .setButton1Text(
+ R.string.bluetooth_device_context_disconnect)
+ .setButton1Icon(R.drawable.ic_settings_close)
+ .setButton1OnClickListener(
+ unused -> {
+ if (mPreference != null) {
+ mPreference.setButton1Enabled(false);
+ }
+ mAudioStreamsHelper.removeSource(mBroadcastId);
+ });
+ }
+ });
+ } else {
+ View.OnClickListener clickToRejoin =
+ unused ->
+ ThreadUtils.postOnBackgroundThread(
+ () -> {
+ var metadata =
+ mAudioStreamsRepository.getSavedMetadata(
+ mContext, mBroadcastId);
+ if (metadata != null) {
+ mAudioStreamsHelper.addSource(metadata);
+ ThreadUtils.postOnMainThread(
+ () -> {
+ if (mPreference != null) {
+ mPreference.setButton1Enabled(
+ false);
+ }
+ });
+ }
+ });
+ ThreadUtils.postOnMainThread(
+ () -> {
+ if (mPreference != null) {
+ mPreference.setButton1Enabled(true);
+ mPreference
+ .setButton1Text(R.string.bluetooth_device_context_connect)
+ .setButton1Icon(R.drawable.ic_add_24dp)
+ .setButton1OnClickListener(clickToRejoin);
+ }
+ });
+ }
+ } else {
+ Log.w(TAG, "updateButton(): preference is null!");
+ }
+ }
+
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderController.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderController.java
index 89f24bc..3524543 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamHeaderController.java
@@ -16,22 +16,64 @@
package com.android.settings.connecteddevice.audiosharing.audiostreams;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeBroadcastAssistant;
+import android.bluetooth.BluetoothLeBroadcastReceiveState;
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.R;
+import com.android.settings.bluetooth.Utils;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.widget.EntityHeaderController;
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
+import com.android.settingslib.utils.ThreadUtils;
import com.android.settingslib.widget.LayoutPreference;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
import javax.annotation.Nullable;
public class AudioStreamHeaderController extends BasePreferenceController
implements DefaultLifecycleObserver {
+ private static final String TAG = "AudioStreamHeaderController";
private static final String KEY = "audio_stream_header";
+ private final Executor mExecutor;
+ private final AudioStreamsHelper mAudioStreamsHelper;
+ @Nullable private final LocalBluetoothLeBroadcastAssistant mLeBroadcastAssistant;
+ private final BluetoothLeBroadcastAssistant.Callback mBroadcastAssistantCallback =
+ new AudioStreamsBroadcastAssistantCallback() {
+ @Override
+ public void onSourceRemoved(BluetoothDevice sink, int sourceId, int reason) {
+ super.onSourceRemoved(sink, sourceId, reason);
+ updateSummary();
+ }
+
+ @Override
+ public void onSourceLost(int broadcastId) {
+ super.onSourceLost(broadcastId);
+ updateSummary();
+ }
+
+ @Override
+ public void onReceiveStateChanged(
+ BluetoothDevice sink,
+ int sourceId,
+ BluetoothLeBroadcastReceiveState state) {
+ super.onReceiveStateChanged(sink, sourceId, state);
+ if (mAudioStreamsHelper.isConnected(state)) {
+ updateSummary();
+ }
+ }
+ };
+
private @Nullable EntityHeaderController mHeaderController;
private @Nullable DashboardFragment mFragment;
private String mBroadcastName = "";
@@ -39,6 +81,27 @@
public AudioStreamHeaderController(Context context, String preferenceKey) {
super(context, preferenceKey);
+ mExecutor = Executors.newSingleThreadExecutor();
+ mAudioStreamsHelper = new AudioStreamsHelper(Utils.getLocalBtManager(context));
+ mLeBroadcastAssistant = mAudioStreamsHelper.getLeBroadcastAssistant();
+ }
+
+ @Override
+ public void onStart(@NonNull LifecycleOwner owner) {
+ if (mLeBroadcastAssistant == null) {
+ Log.w(TAG, "onStart(): LeBroadcastAssistant is null!");
+ return;
+ }
+ mLeBroadcastAssistant.registerServiceCallBack(mExecutor, mBroadcastAssistantCallback);
+ }
+
+ @Override
+ public void onStop(@NonNull LifecycleOwner owner) {
+ if (mLeBroadcastAssistant == null) {
+ Log.w(TAG, "onStop(): LeBroadcastAssistant is null!");
+ return;
+ }
+ mLeBroadcastAssistant.unregisterServiceCallBack(mBroadcastAssistantCallback);
}
@Override
@@ -55,14 +118,37 @@
}
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);
+ updateSummary();
}
super.displayPreference(screen);
}
+ private void updateSummary() {
+ var unused =
+ ThreadUtils.postOnBackgroundThread(
+ () -> {
+ var latestSummary =
+ mAudioStreamsHelper.getAllConnectedSources().stream()
+ .map(
+ BluetoothLeBroadcastReceiveState
+ ::getBroadcastId)
+ .anyMatch(
+ connectedBroadcastId ->
+ connectedBroadcastId
+ == mBroadcastId)
+ ? "Listening now"
+ : "";
+ ThreadUtils.postOnMainThread(
+ () -> {
+ if (mHeaderController != null) {
+ mHeaderController.setSummary(latestSummary);
+ mHeaderController.done(true);
+ }
+ });
+ });
+ }
+
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsBroadcastAssistantCallback.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsBroadcastAssistantCallback.java
index 84e753c..9fb5b21 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsBroadcastAssistantCallback.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsBroadcastAssistantCallback.java
@@ -24,21 +24,12 @@
import com.android.settingslib.bluetooth.BluetoothUtils;
-import java.util.Locale;
-
public class AudioStreamsBroadcastAssistantCallback
implements BluetoothLeBroadcastAssistant.Callback {
private static final String TAG = "AudioStreamsBroadcastAssistantCallback";
private static final boolean DEBUG = BluetoothUtils.D;
- private final AudioStreamsProgressCategoryController mCategoryController;
-
- public AudioStreamsBroadcastAssistantCallback(
- AudioStreamsProgressCategoryController audioStreamsProgressCategoryController) {
- mCategoryController = audioStreamsProgressCategoryController;
- }
-
@Override
public void onReceiveStateChanged(
BluetoothDevice sink, int sourceId, BluetoothLeBroadcastReceiveState state) {
@@ -52,45 +43,30 @@
+ " state: "
+ state);
}
- mCategoryController.handleSourceConnected(state);
}
@Override
public void onSearchStartFailed(int reason) {
Log.w(TAG, "onSearchStartFailed() reason : " + reason);
- mCategoryController.showToast(
- String.format(Locale.US, "Failed to start scanning, reason %d", reason));
}
@Override
public void onSearchStarted(int reason) {
- if (mCategoryController == null) {
- Log.w(TAG, "onSearchStarted() : mCategoryController is null!");
- return;
- }
if (DEBUG) {
Log.d(TAG, "onSearchStarted() reason : " + reason);
}
- mCategoryController.setScanning(true);
}
@Override
public void onSearchStopFailed(int reason) {
Log.w(TAG, "onSearchStopFailed() reason : " + reason);
- mCategoryController.showToast(
- String.format(Locale.US, "Failed to stop scanning, reason %d", reason));
}
@Override
public void onSearchStopped(int reason) {
- if (mCategoryController == null) {
- Log.w(TAG, "onSearchStopped() : mCategoryController is null!");
- return;
- }
if (DEBUG) {
Log.d(TAG, "onSearchStopped() reason : " + reason);
}
- mCategoryController.setScanning(false);
}
@Override
@@ -106,8 +82,6 @@
+ " reason: "
+ reason);
}
- mCategoryController.showToast(
- String.format(Locale.US, "Failed to join broadcast, reason %d", reason));
}
@Override
@@ -126,14 +100,9 @@
@Override
public void onSourceFound(BluetoothLeBroadcastMetadata source) {
- if (mCategoryController == null) {
- Log.w(TAG, "onSourceFound() : mCategoryController is null!");
- return;
- }
if (DEBUG) {
Log.d(TAG, "onSourceFound() broadcastId : " + source.getBroadcastId());
}
- mCategoryController.handleSourceFound(source);
}
@Override
@@ -141,7 +110,6 @@
if (DEBUG) {
Log.d(TAG, "onSourceLost() broadcastId : " + broadcastId);
}
- mCategoryController.handleSourceLost(broadcastId);
}
@Override
@@ -153,12 +121,6 @@
@Override
public void onSourceRemoveFailed(BluetoothDevice sink, int sourceId, int reason) {
Log.w(TAG, "onSourceRemoveFailed() sourceId : " + sourceId + " reason : " + reason);
- mCategoryController.showToast(
- String.format(
- Locale.US,
- "Failed to remove source %d for sink %s",
- sourceId,
- sink.getAddress()));
}
@Override
@@ -166,8 +128,5 @@
if (DEBUG) {
Log.d(TAG, "onSourceRemoved() sourceId : " + sourceId + " reason : " + reason);
}
- mCategoryController.showToast(
- String.format(
- Locale.US, "Source %d removed for sink %s", sourceId, sink.getAddress()));
}
}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallback.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallback.java
new file mode 100644
index 0000000..15a0603
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryCallback.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2024 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.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeBroadcastMetadata;
+import android.bluetooth.BluetoothLeBroadcastReceiveState;
+import android.util.Log;
+
+import java.util.Locale;
+
+public class AudioStreamsProgressCategoryCallback extends AudioStreamsBroadcastAssistantCallback {
+ private static final String TAG = "AudioStreamsProgressCategoryCallback";
+
+ private final AudioStreamsProgressCategoryController mCategoryController;
+
+ public AudioStreamsProgressCategoryCallback(
+ AudioStreamsProgressCategoryController audioStreamsProgressCategoryController) {
+ mCategoryController = audioStreamsProgressCategoryController;
+ }
+
+ @Override
+ public void onReceiveStateChanged(
+ BluetoothDevice sink, int sourceId, BluetoothLeBroadcastReceiveState state) {
+ super.onReceiveStateChanged(sink, sourceId, state);
+ mCategoryController.handleSourceConnected(state);
+ }
+
+ @Override
+ public void onSearchStartFailed(int reason) {
+ super.onSearchStartFailed(reason);
+ mCategoryController.showToast(
+ String.format(Locale.US, "Failed to start scanning, reason %d", reason));
+ }
+
+ @Override
+ public void onSearchStarted(int reason) {
+ super.onSearchStarted(reason);
+ if (mCategoryController == null) {
+ Log.w(TAG, "onSearchStarted() : mCategoryController is null!");
+ return;
+ }
+ mCategoryController.setScanning(true);
+ }
+
+ @Override
+ public void onSearchStopFailed(int reason) {
+ super.onSearchStopFailed(reason);
+ mCategoryController.showToast(
+ String.format(Locale.US, "Failed to stop scanning, reason %d", reason));
+ }
+
+ @Override
+ public void onSearchStopped(int reason) {
+ super.onSearchStopped(reason);
+ if (mCategoryController == null) {
+ Log.w(TAG, "onSearchStopped() : mCategoryController is null!");
+ return;
+ }
+ mCategoryController.setScanning(false);
+ }
+
+ @Override
+ public void onSourceAddFailed(
+ BluetoothDevice sink, BluetoothLeBroadcastMetadata source, int reason) {
+ super.onSourceAddFailed(sink, source, reason);
+ mCategoryController.showToast(
+ String.format(Locale.US, "Failed to join broadcast, reason %d", reason));
+ }
+
+ @Override
+ public void onSourceFound(BluetoothLeBroadcastMetadata source) {
+ super.onSourceFound(source);
+ if (mCategoryController == null) {
+ Log.w(TAG, "onSourceFound() : mCategoryController is null!");
+ return;
+ }
+ mCategoryController.handleSourceFound(source);
+ }
+
+ @Override
+ public void onSourceLost(int broadcastId) {
+ super.onSourceLost(broadcastId);
+ mCategoryController.handleSourceLost(broadcastId);
+ }
+
+ @Override
+ public void onSourceRemoveFailed(BluetoothDevice sink, int sourceId, int reason) {
+ super.onSourceRemoveFailed(sink, sourceId, reason);
+ mCategoryController.showToast(
+ String.format(
+ Locale.US,
+ "Failed to remove source %d for sink %s",
+ sourceId,
+ sink.getAddress()));
+ }
+
+ @Override
+ public void onSourceRemoved(BluetoothDevice sink, int sourceId, int reason) {
+ super.onSourceRemoved(sink, sourceId, reason);
+ mCategoryController.showToast(
+ String.format(
+ Locale.US, "Source %d removed for sink %s", sourceId, sink.getAddress()));
+ }
+}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java
index cb9975d..b3b0743 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsProgressCategoryController.java
@@ -74,6 +74,9 @@
}
};
+ private final AudioStreamsRepository mAudioStreamsRepository =
+ AudioStreamsRepository.getInstance();
+
enum AudioStreamState {
// When mTimedSourceFromQrCode is present and this source has not been synced.
WAIT_FOR_SYNC,
@@ -86,7 +89,7 @@
}
private final Executor mExecutor;
- private final AudioStreamsBroadcastAssistantCallback mBroadcastAssistantCallback;
+ private final AudioStreamsProgressCategoryCallback mBroadcastAssistantCallback;
private final AudioStreamsHelper mAudioStreamsHelper;
private final @Nullable LocalBluetoothLeBroadcastAssistant mLeBroadcastAssistant;
private final @Nullable LocalBluetoothManager mBluetoothManager;
@@ -102,7 +105,7 @@
mBluetoothManager = Utils.getLocalBtManager(mContext);
mAudioStreamsHelper = new AudioStreamsHelper(mBluetoothManager);
mLeBroadcastAssistant = mAudioStreamsHelper.getLeBroadcastAssistant();
- mBroadcastAssistantCallback = new AudioStreamsBroadcastAssistantCallback(this);
+ mBroadcastAssistantCallback = new AudioStreamsProgressCategoryCallback(this);
}
@Override
@@ -170,6 +173,7 @@
source, (AudioStreamPreference) preference));
} else {
mAudioStreamsHelper.addSource(source);
+ mAudioStreamsRepository.cacheMetadata(source);
((AudioStreamPreference) preference)
.setAudioStreamState(AudioStreamState.WAIT_FOR_SOURCE_ADD);
updatePreferenceConnectionState(
@@ -202,6 +206,7 @@
return v;
}
mAudioStreamsHelper.addSource(pendingSource);
+ mAudioStreamsRepository.cacheMetadata(pendingSource);
mTimedSourceFromQrCode.consumed();
v.setAudioStreamState(AudioStreamState.WAIT_FOR_SOURCE_ADD);
updatePreferenceConnectionState(
@@ -236,6 +241,7 @@
var fromState = v.getAudioStreamState();
if (fromState == AudioStreamState.SYNCED) {
mAudioStreamsHelper.addSource(metadataFromQrCode);
+ mAudioStreamsRepository.cacheMetadata(metadataFromQrCode);
mTimedSourceFromQrCode.consumed();
v.setAudioStreamState(AudioStreamState.WAIT_FOR_SOURCE_ADD);
updatePreferenceConnectionState(
@@ -302,6 +308,16 @@
v, sourceAddedState, p -> launchDetailFragment(broadcastIdConnected));
return v;
});
+ // Saved connected metadata for user to re-join this broadcast later.
+ var unused =
+ ThreadUtils.postOnBackgroundThread(
+ () -> {
+ var cached =
+ mAudioStreamsRepository.getCachedMetadata(broadcastIdConnected);
+ if (cached != null) {
+ mAudioStreamsRepository.saveMetadata(mContext, cached);
+ }
+ });
}
private static String getPreferenceSummary(AudioStreamState state) {
@@ -457,11 +473,13 @@
R.id.broadcast_edit_text))
.getText()
.toString();
- mAudioStreamsHelper.addSource(
+ var metadata =
new BluetoothLeBroadcastMetadata.Builder(source)
.setBroadcastCode(
code.getBytes(StandardCharsets.UTF_8))
- .build());
+ .build();
+ mAudioStreamsHelper.addSource(metadata);
+ mAudioStreamsRepository.cacheMetadata(metadata);
preference.setAudioStreamState(
AudioStreamState.WAIT_FOR_SOURCE_ADD);
updatePreferenceConnectionState(
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsRepository.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsRepository.java
new file mode 100644
index 0000000..65245ac
--- /dev/null
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsRepository.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2024 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.bluetooth.BluetoothLeBroadcastMetadata;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.util.Log;
+
+import com.android.settingslib.bluetooth.BluetoothLeBroadcastMetadataExt;
+import com.android.settingslib.bluetooth.BluetoothUtils;
+import com.android.settingslib.utils.ThreadUtils;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.annotation.Nullable;
+
+/** Manages the caching and storage of Bluetooth audio stream metadata. */
+public class AudioStreamsRepository {
+
+ private static final String TAG = "AudioStreamsRepository";
+ private static final boolean DEBUG = BluetoothUtils.D;
+
+ private static final String PREF_KEY = "bluetooth_audio_stream_pref";
+ private static final String METADATA_KEY = "bluetooth_audio_stream_metadata";
+
+ @Nullable
+ private static AudioStreamsRepository sInstance = null;
+
+ private AudioStreamsRepository() {}
+
+ /**
+ * Gets the single instance of AudioStreamsRepository.
+ *
+ * @return The AudioStreamsRepository instance.
+ */
+ public static synchronized AudioStreamsRepository getInstance() {
+ if (sInstance == null) {
+ sInstance = new AudioStreamsRepository();
+ }
+ return sInstance;
+ }
+
+ private final ConcurrentHashMap<Integer, BluetoothLeBroadcastMetadata>
+ mBroadcastIdToMetadataCacheMap = new ConcurrentHashMap<>();
+
+ /**
+ * Caches BluetoothLeBroadcastMetadata in a local cache.
+ *
+ * @param metadata The BluetoothLeBroadcastMetadata to be cached.
+ */
+ void cacheMetadata(BluetoothLeBroadcastMetadata metadata) {
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "cacheMetadata(): broadcastId "
+ + metadata.getBroadcastId()
+ + " saved in local cache.");
+ }
+ mBroadcastIdToMetadataCacheMap.put(metadata.getBroadcastId(), metadata);
+ }
+
+ /**
+ * Gets cached BluetoothLeBroadcastMetadata by broadcastId.
+ *
+ * @param broadcastId The broadcastId to look up in the cache.
+ * @return The cached BluetoothLeBroadcastMetadata or null if not found.
+ */
+ @Nullable
+ BluetoothLeBroadcastMetadata getCachedMetadata(int broadcastId) {
+ var metadata = mBroadcastIdToMetadataCacheMap.get(broadcastId);
+ if (metadata == null) {
+ Log.w(
+ TAG,
+ "getCachedMetadata(): broadcastId not found in"
+ + " mBroadcastIdToMetadataCacheMap.");
+ return null;
+ }
+ return metadata;
+ }
+
+ /**
+ * Saves metadata to SharedPreferences asynchronously.
+ *
+ * @param context The context.
+ * @param metadata The BluetoothLeBroadcastMetadata to be saved.
+ */
+ void saveMetadata(Context context, BluetoothLeBroadcastMetadata metadata) {
+ var unused =
+ ThreadUtils.postOnBackgroundThread(
+ () -> {
+ SharedPreferences sharedPref =
+ context.getSharedPreferences(PREF_KEY, Context.MODE_PRIVATE);
+ if (sharedPref != null) {
+ SharedPreferences.Editor editor = sharedPref.edit();
+ editor.putString(
+ METADATA_KEY,
+ BluetoothLeBroadcastMetadataExt.INSTANCE.toQrCodeString(
+ metadata));
+ editor.apply();
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "saveMetadata(): broadcastId "
+ + metadata.getBroadcastId()
+ + " metadata saved in storage.");
+ }
+ }
+ });
+ }
+
+ /**
+ * Gets saved metadata from SharedPreferences.
+ *
+ * @param context The context.
+ * @param broadcastId The broadcastId to retrieve metadata for.
+ * @return The saved BluetoothLeBroadcastMetadata or null if not found.
+ */
+ @Nullable
+ BluetoothLeBroadcastMetadata getSavedMetadata(Context context, int broadcastId) {
+ SharedPreferences sharedPref = context.getSharedPreferences(PREF_KEY, Context.MODE_PRIVATE);
+ if (sharedPref != null) {
+ String savedMetadataStr = sharedPref.getString(METADATA_KEY, null);
+ if (savedMetadataStr == null) {
+ Log.w(TAG, "getSavedMetadata(): savedMetadataStr is null");
+ return null;
+ }
+ var savedMetadata =
+ BluetoothLeBroadcastMetadataExt.INSTANCE.convertToBroadcastMetadata(
+ savedMetadataStr);
+ if (savedMetadata == null || savedMetadata.getBroadcastId() != broadcastId) {
+ Log.w(TAG, "getSavedMetadata(): savedMetadata doesn't match broadcast Id.");
+ return null;
+ }
+ if (DEBUG) {
+ Log.d(
+ TAG,
+ "getSavedMetadata(): broadcastId "
+ + savedMetadata.getBroadcastId()
+ + " metadata found in storage.");
+ }
+ return savedMetadata;
+ }
+ return null;
+ }
+}
diff --git a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsScanQrCodeController.java b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsScanQrCodeController.java
index 549e725..e006cec 100644
--- a/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsScanQrCodeController.java
+++ b/src/com/android/settings/connecteddevice/audiosharing/audiostreams/AudioStreamsScanQrCodeController.java
@@ -16,7 +16,6 @@
package com.android.settings.connecteddevice.audiosharing.audiostreams;
-import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.Intent;
@@ -124,10 +123,6 @@
});
}
- void addSource(BluetoothLeBroadcastMetadata source) {
- mAudioStreamsHelper.addSource(source);
- }
-
private void updateVisibility() {
ThreadUtils.postOnBackgroundThread(
() -> {