[Audiosharing] Handle sync, add source via qrcode.
Bug: 305620450 Test: manual Change-Id: I32c14607035d8f37f44186175657c42307780e7b
This commit is contained in:
@@ -35,21 +35,27 @@ import com.google.common.base.Strings;
|
|||||||
*/
|
*/
|
||||||
class AudioStreamPreference extends TwoTargetPreference {
|
class AudioStreamPreference extends TwoTargetPreference {
|
||||||
private boolean mIsConnected = false;
|
private boolean mIsConnected = false;
|
||||||
|
private AudioStream mAudioStream;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update preference UI based on connection status
|
* Update preference UI based on connection status
|
||||||
*
|
*
|
||||||
* @param isConnected Is this streams connected
|
* @param isConnected Is this stream connected
|
||||||
|
* @param summary Summary text
|
||||||
|
* @param onPreferenceClickListener Click listener for the preference
|
||||||
*/
|
*/
|
||||||
void setIsConnected(
|
void setIsConnected(
|
||||||
boolean isConnected, @Nullable OnPreferenceClickListener onPreferenceClickListener) {
|
boolean isConnected,
|
||||||
|
String summary,
|
||||||
|
@Nullable OnPreferenceClickListener onPreferenceClickListener) {
|
||||||
if (mIsConnected == isConnected
|
if (mIsConnected == isConnected
|
||||||
|
&& getSummary() == summary
|
||||||
&& getOnPreferenceClickListener() == onPreferenceClickListener) {
|
&& getOnPreferenceClickListener() == onPreferenceClickListener) {
|
||||||
// Nothing to update.
|
// Nothing to update.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mIsConnected = isConnected;
|
mIsConnected = isConnected;
|
||||||
setSummary(isConnected ? "Listening now" : "");
|
setSummary(summary);
|
||||||
setOrder(isConnected ? 0 : 1);
|
setOrder(isConnected ? 0 : 1);
|
||||||
setOnPreferenceClickListener(onPreferenceClickListener);
|
setOnPreferenceClickListener(onPreferenceClickListener);
|
||||||
notifyChanged();
|
notifyChanged();
|
||||||
@@ -60,6 +66,14 @@ class AudioStreamPreference extends TwoTargetPreference {
|
|||||||
setIcon(R.drawable.ic_bt_audio_sharing);
|
setIcon(R.drawable.ic_bt_audio_sharing);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setAudioStreamState(AudioStreamsProgressCategoryController.AudioStreamState state) {
|
||||||
|
mAudioStream.setState(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioStreamsProgressCategoryController.AudioStreamState getAudioStreamState() {
|
||||||
|
return mAudioStream.getState();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean shouldHideSecondTarget() {
|
protected boolean shouldHideSecondTarget() {
|
||||||
return mIsConnected;
|
return mIsConnected;
|
||||||
@@ -71,19 +85,31 @@ class AudioStreamPreference extends TwoTargetPreference {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static AudioStreamPreference fromMetadata(
|
static AudioStreamPreference fromMetadata(
|
||||||
Context context, BluetoothLeBroadcastMetadata source) {
|
Context context,
|
||||||
|
BluetoothLeBroadcastMetadata source,
|
||||||
|
AudioStreamsProgressCategoryController.AudioStreamState streamState) {
|
||||||
AudioStreamPreference preference = new AudioStreamPreference(context, /* attrs= */ null);
|
AudioStreamPreference preference = new AudioStreamPreference(context, /* attrs= */ null);
|
||||||
preference.setTitle(getBroadcastName(source));
|
preference.setTitle(getBroadcastName(source));
|
||||||
|
preference.setAudioStream(new AudioStream(source.getBroadcastId(), streamState));
|
||||||
return preference;
|
return preference;
|
||||||
}
|
}
|
||||||
|
|
||||||
static AudioStreamPreference fromReceiveState(
|
static AudioStreamPreference fromReceiveState(
|
||||||
Context context, BluetoothLeBroadcastReceiveState state) {
|
Context context,
|
||||||
|
BluetoothLeBroadcastReceiveState receiveState,
|
||||||
|
AudioStreamsProgressCategoryController.AudioStreamState streamState) {
|
||||||
AudioStreamPreference preference = new AudioStreamPreference(context, /* attrs= */ null);
|
AudioStreamPreference preference = new AudioStreamPreference(context, /* attrs= */ null);
|
||||||
preference.setTitle(getBroadcastName(state));
|
preference.setTitle(getBroadcastName(receiveState));
|
||||||
|
preference.setAudioStream(
|
||||||
|
new AudioStream(
|
||||||
|
receiveState.getSourceId(), receiveState.getBroadcastId(), streamState));
|
||||||
return preference;
|
return preference;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setAudioStream(AudioStream audioStream) {
|
||||||
|
mAudioStream = audioStream;
|
||||||
|
}
|
||||||
|
|
||||||
private static String getBroadcastName(BluetoothLeBroadcastMetadata source) {
|
private static String getBroadcastName(BluetoothLeBroadcastMetadata source) {
|
||||||
return source.getSubgroups().stream()
|
return source.getSubgroups().stream()
|
||||||
.map(s -> s.getContentMetadata().getProgramInfo())
|
.map(s -> s.getContentMetadata().getProgramInfo())
|
||||||
@@ -99,4 +125,43 @@ class AudioStreamPreference extends TwoTargetPreference {
|
|||||||
.findFirst()
|
.findFirst()
|
||||||
.orElse("Broadcast Id: " + state.getBroadcastId());
|
.orElse("Broadcast Id: " + state.getBroadcastId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final class AudioStream {
|
||||||
|
private int mSourceId;
|
||||||
|
private int mBroadcastId;
|
||||||
|
private AudioStreamsProgressCategoryController.AudioStreamState mState;
|
||||||
|
|
||||||
|
private AudioStream(
|
||||||
|
int broadcastId, AudioStreamsProgressCategoryController.AudioStreamState state) {
|
||||||
|
mBroadcastId = broadcastId;
|
||||||
|
mState = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AudioStream(
|
||||||
|
int sourceId,
|
||||||
|
int broadcastId,
|
||||||
|
AudioStreamsProgressCategoryController.AudioStreamState state) {
|
||||||
|
mSourceId = sourceId;
|
||||||
|
mBroadcastId = broadcastId;
|
||||||
|
mState = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(chelseahao): use this to handleSourceRemoved
|
||||||
|
private int getSourceId() {
|
||||||
|
return mSourceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(chelseahao): use this to handleSourceRemoved
|
||||||
|
private int getBroadcastId() {
|
||||||
|
return mBroadcastId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AudioStreamsProgressCategoryController.AudioStreamState getState() {
|
||||||
|
return mState;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setState(AudioStreamsProgressCategoryController.AudioStreamState state) {
|
||||||
|
mState = state;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -34,7 +34,7 @@ import com.android.settingslib.bluetooth.BluetoothUtils;
|
|||||||
public class AudioStreamsDashboardFragment extends DashboardFragment {
|
public class AudioStreamsDashboardFragment extends DashboardFragment {
|
||||||
private static final String TAG = "AudioStreamsDashboardFrag";
|
private static final String TAG = "AudioStreamsDashboardFrag";
|
||||||
private static final boolean DEBUG = BluetoothUtils.D;
|
private static final boolean DEBUG = BluetoothUtils.D;
|
||||||
private AudioStreamsScanQrCodeController mAudioStreamsScanQrCodeController;
|
private AudioStreamsProgressCategoryController mAudioStreamsProgressCategoryController;
|
||||||
|
|
||||||
public AudioStreamsDashboardFragment() {
|
public AudioStreamsDashboardFragment() {
|
||||||
super();
|
super();
|
||||||
@@ -69,8 +69,8 @@ public class AudioStreamsDashboardFragment extends DashboardFragment {
|
|||||||
@Override
|
@Override
|
||||||
public void onAttach(Context context) {
|
public void onAttach(Context context) {
|
||||||
super.onAttach(context);
|
super.onAttach(context);
|
||||||
mAudioStreamsScanQrCodeController = use(AudioStreamsScanQrCodeController.class);
|
use(AudioStreamsScanQrCodeController.class).setFragment(this);
|
||||||
mAudioStreamsScanQrCodeController.setFragment(this);
|
mAudioStreamsProgressCategoryController = use(AudioStreamsProgressCategoryController.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -103,11 +103,13 @@ public class AudioStreamsDashboardFragment extends DashboardFragment {
|
|||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "onActivityResult() broadcastId : " + source.getBroadcastId());
|
Log.d(TAG, "onActivityResult() broadcastId : " + source.getBroadcastId());
|
||||||
}
|
}
|
||||||
if (mAudioStreamsScanQrCodeController == null) {
|
if (mAudioStreamsProgressCategoryController == null) {
|
||||||
Log.w(TAG, "onActivityResult() AudioStreamsScanQrCodeController is null!");
|
Log.w(
|
||||||
|
TAG,
|
||||||
|
"onActivityResult() AudioStreamsProgressCategoryController is null!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mAudioStreamsScanQrCodeController.addSource(source);
|
mAudioStreamsProgressCategoryController.setSourceFromQrCode(source);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -109,13 +109,14 @@ class AudioStreamsHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Retrieves a list of all LE broadcast receive states from active sinks. */
|
/** Retrieves a list of all LE broadcast receive states from active sinks. */
|
||||||
List<BluetoothLeBroadcastReceiveState> getAllSources() {
|
List<BluetoothLeBroadcastReceiveState> getAllConnectedSources() {
|
||||||
if (mLeBroadcastAssistant == null) {
|
if (mLeBroadcastAssistant == null) {
|
||||||
Log.w(TAG, "getAllSources(): LeBroadcastAssistant is null!");
|
Log.w(TAG, "getAllSources(): LeBroadcastAssistant is null!");
|
||||||
return emptyList();
|
return emptyList();
|
||||||
}
|
}
|
||||||
return getActiveSinksOnAssistant(mBluetoothManager).stream()
|
return getActiveSinksOnAssistant(mBluetoothManager).stream()
|
||||||
.flatMap(sink -> mLeBroadcastAssistant.getAllSources(sink).stream())
|
.flatMap(sink -> mLeBroadcastAssistant.getAllSources(sink).stream())
|
||||||
|
.filter(this::isConnected)
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,7 +125,7 @@ class AudioStreamsHelper {
|
|||||||
return mLeBroadcastAssistant;
|
return mLeBroadcastAssistant;
|
||||||
}
|
}
|
||||||
|
|
||||||
static boolean isConnected(BluetoothLeBroadcastReceiveState state) {
|
boolean isConnected(BluetoothLeBroadcastReceiveState state) {
|
||||||
return state.getPaSyncState() == BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED
|
return state.getPaSyncState() == BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED
|
||||||
&& state.getBigEncryptionState()
|
&& state.getBigEncryptionState()
|
||||||
== BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING;
|
== BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING;
|
||||||
|
@@ -25,6 +25,7 @@ import android.bluetooth.BluetoothLeBroadcastReceiveState;
|
|||||||
import android.bluetooth.BluetoothProfile;
|
import android.bluetooth.BluetoothProfile;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.CountDownTimer;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -71,6 +72,17 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum AudioStreamState {
|
||||||
|
// When mTimedSourceFromQrCode is present and this source has not been synced.
|
||||||
|
WAIT_FOR_SYNC,
|
||||||
|
// When source has been synced but not added to any sink.
|
||||||
|
SYNCED,
|
||||||
|
// When addSource is called for this source and waiting for response.
|
||||||
|
WAIT_FOR_SOURCE_ADD,
|
||||||
|
// Source is added to active sink.
|
||||||
|
SOURCE_ADDED,
|
||||||
|
}
|
||||||
|
|
||||||
private final Executor mExecutor;
|
private final Executor mExecutor;
|
||||||
private final AudioStreamsBroadcastAssistantCallback mBroadcastAssistantCallback;
|
private final AudioStreamsBroadcastAssistantCallback mBroadcastAssistantCallback;
|
||||||
private final AudioStreamsHelper mAudioStreamsHelper;
|
private final AudioStreamsHelper mAudioStreamsHelper;
|
||||||
@@ -78,6 +90,7 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
|
|||||||
private final @Nullable LocalBluetoothManager mBluetoothManager;
|
private final @Nullable LocalBluetoothManager mBluetoothManager;
|
||||||
private final ConcurrentHashMap<Integer, AudioStreamPreference> mBroadcastIdToPreferenceMap =
|
private final ConcurrentHashMap<Integer, AudioStreamPreference> mBroadcastIdToPreferenceMap =
|
||||||
new ConcurrentHashMap<>();
|
new ConcurrentHashMap<>();
|
||||||
|
private TimedSourceFromQrCode mTimedSourceFromQrCode;
|
||||||
private AudioStreamsProgressCategoryPreference mCategoryPreference;
|
private AudioStreamsProgressCategoryPreference mCategoryPreference;
|
||||||
|
|
||||||
public AudioStreamsProgressCategoryController(Context context, String preferenceKey) {
|
public AudioStreamsProgressCategoryController(Context context, String preferenceKey) {
|
||||||
@@ -122,6 +135,12 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
|
|||||||
mExecutor.execute(this::stopScanning);
|
mExecutor.execute(this::stopScanning);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setSourceFromQrCode(BluetoothLeBroadcastMetadata source) {
|
||||||
|
mTimedSourceFromQrCode =
|
||||||
|
new TimedSourceFromQrCode(
|
||||||
|
mContext, source, () -> handleSourceLost(source.getBroadcastId()));
|
||||||
|
}
|
||||||
|
|
||||||
void setScanning(boolean isScanning) {
|
void setScanning(boolean isScanning) {
|
||||||
ThreadUtils.postOnMainThread(
|
ThreadUtils.postOnMainThread(
|
||||||
() -> {
|
() -> {
|
||||||
@@ -140,24 +159,90 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
|
|||||||
}
|
}
|
||||||
if (source.isEncrypted()) {
|
if (source.isEncrypted()) {
|
||||||
ThreadUtils.postOnMainThread(
|
ThreadUtils.postOnMainThread(
|
||||||
() -> launchPasswordDialog(source, preference));
|
() ->
|
||||||
|
launchPasswordDialog(
|
||||||
|
source, (AudioStreamPreference) preference));
|
||||||
} else {
|
} else {
|
||||||
mAudioStreamsHelper.addSource(source);
|
mAudioStreamsHelper.addSource(source);
|
||||||
|
((AudioStreamPreference) preference)
|
||||||
|
.setAudioStreamState(AudioStreamState.WAIT_FOR_SOURCE_ADD);
|
||||||
|
updatePreferenceConnectionState(
|
||||||
|
(AudioStreamPreference) preference,
|
||||||
|
AudioStreamState.WAIT_FOR_SOURCE_ADD,
|
||||||
|
null);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
mBroadcastIdToPreferenceMap.computeIfAbsent(
|
|
||||||
source.getBroadcastId(),
|
var broadcastIdFound = source.getBroadcastId();
|
||||||
k -> {
|
mBroadcastIdToPreferenceMap.compute(
|
||||||
var preference = AudioStreamPreference.fromMetadata(mContext, source);
|
broadcastIdFound,
|
||||||
ThreadUtils.postOnMainThread(
|
(k, v) -> {
|
||||||
() -> {
|
if (v == null) {
|
||||||
preference.setIsConnected(false, addSourceOrShowDialog);
|
return addNewPreference(
|
||||||
if (mCategoryPreference != null) {
|
source, AudioStreamState.SYNCED, addSourceOrShowDialog);
|
||||||
mCategoryPreference.addPreference(preference);
|
}
|
||||||
}
|
var fromState = v.getAudioStreamState();
|
||||||
});
|
if (fromState == AudioStreamState.WAIT_FOR_SYNC) {
|
||||||
return preference;
|
var pendingSource = mTimedSourceFromQrCode.get();
|
||||||
|
if (pendingSource == null) {
|
||||||
|
Log.w(
|
||||||
|
TAG,
|
||||||
|
"handleSourceFound(): unexpected state with null pendingSource:"
|
||||||
|
+ fromState
|
||||||
|
+ " for broadcastId : "
|
||||||
|
+ broadcastIdFound);
|
||||||
|
v.setAudioStreamState(AudioStreamState.SYNCED);
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
mAudioStreamsHelper.addSource(pendingSource);
|
||||||
|
mTimedSourceFromQrCode.consumed();
|
||||||
|
v.setAudioStreamState(AudioStreamState.WAIT_FOR_SOURCE_ADD);
|
||||||
|
updatePreferenceConnectionState(
|
||||||
|
v, AudioStreamState.WAIT_FOR_SOURCE_ADD, null);
|
||||||
|
} else {
|
||||||
|
if (fromState != AudioStreamState.SOURCE_ADDED) {
|
||||||
|
Log.w(
|
||||||
|
TAG,
|
||||||
|
"handleSourceFound(): unexpected state : "
|
||||||
|
+ fromState
|
||||||
|
+ " for broadcastId : "
|
||||||
|
+ broadcastIdFound);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleSourceFromQrCodeIfExists() {
|
||||||
|
if (mTimedSourceFromQrCode == null || mTimedSourceFromQrCode.get() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var metadataFromQrCode = mTimedSourceFromQrCode.get();
|
||||||
|
mBroadcastIdToPreferenceMap.compute(
|
||||||
|
metadataFromQrCode.getBroadcastId(),
|
||||||
|
(k, v) -> {
|
||||||
|
if (v == null) {
|
||||||
|
mTimedSourceFromQrCode.waitForConsume();
|
||||||
|
return addNewPreference(
|
||||||
|
metadataFromQrCode, AudioStreamState.WAIT_FOR_SYNC, null);
|
||||||
|
}
|
||||||
|
var fromState = v.getAudioStreamState();
|
||||||
|
if (fromState == AudioStreamState.SYNCED) {
|
||||||
|
mAudioStreamsHelper.addSource(metadataFromQrCode);
|
||||||
|
mTimedSourceFromQrCode.consumed();
|
||||||
|
v.setAudioStreamState(AudioStreamState.WAIT_FOR_SOURCE_ADD);
|
||||||
|
updatePreferenceConnectionState(
|
||||||
|
v, AudioStreamState.WAIT_FOR_SOURCE_ADD, null);
|
||||||
|
} else {
|
||||||
|
Log.w(
|
||||||
|
TAG,
|
||||||
|
"handleSourceFromQrCode(): unexpected state : "
|
||||||
|
+ fromState
|
||||||
|
+ " for broadcastId : "
|
||||||
|
+ metadataFromQrCode.getBroadcastId());
|
||||||
|
}
|
||||||
|
return v;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,32 +259,54 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
|
|||||||
mAudioStreamsHelper.removeSource(broadcastId);
|
mAudioStreamsHelper.removeSource(broadcastId);
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleSourceConnected(BluetoothLeBroadcastReceiveState state) {
|
void handleSourceConnected(BluetoothLeBroadcastReceiveState receiveState) {
|
||||||
if (!AudioStreamsHelper.isConnected(state)) {
|
if (!mAudioStreamsHelper.isConnected(receiveState)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
var sourceAddedState = AudioStreamState.SOURCE_ADDED;
|
||||||
|
var broadcastIdConnected = receiveState.getBroadcastId();
|
||||||
mBroadcastIdToPreferenceMap.compute(
|
mBroadcastIdToPreferenceMap.compute(
|
||||||
state.getBroadcastId(),
|
broadcastIdConnected,
|
||||||
(k, v) -> {
|
(k, v) -> {
|
||||||
// True if this source has been added either by scanning, or it's currently
|
if (v == null) {
|
||||||
// connected to another active sink.
|
return addNewPreference(
|
||||||
boolean existed = v != null;
|
receiveState,
|
||||||
AudioStreamPreference preference =
|
sourceAddedState,
|
||||||
existed ? v : AudioStreamPreference.fromReceiveState(mContext, state);
|
p -> launchDetailFragment(broadcastIdConnected));
|
||||||
|
}
|
||||||
ThreadUtils.postOnMainThread(
|
var fromState = v.getAudioStreamState();
|
||||||
() -> {
|
if (fromState == AudioStreamState.WAIT_FOR_SOURCE_ADD
|
||||||
preference.setIsConnected(
|
|| fromState == AudioStreamState.SYNCED
|
||||||
true, p -> launchDetailFragment(state.getBroadcastId()));
|
|| fromState == AudioStreamState.WAIT_FOR_SYNC) {
|
||||||
if (mCategoryPreference != null && !existed) {
|
if (mTimedSourceFromQrCode != null) {
|
||||||
mCategoryPreference.addPreference(preference);
|
mTimedSourceFromQrCode.consumed();
|
||||||
}
|
}
|
||||||
});
|
} else {
|
||||||
|
if (fromState != AudioStreamState.SOURCE_ADDED) {
|
||||||
return preference;
|
Log.w(
|
||||||
|
TAG,
|
||||||
|
"handleSourceConnected(): unexpected state : "
|
||||||
|
+ fromState
|
||||||
|
+ " for broadcastId : "
|
||||||
|
+ broadcastIdConnected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v.setAudioStreamState(sourceAddedState);
|
||||||
|
updatePreferenceConnectionState(
|
||||||
|
v, sourceAddedState, p -> launchDetailFragment(broadcastIdConnected));
|
||||||
|
return v;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String getPreferenceSummary(AudioStreamState state) {
|
||||||
|
return switch (state) {
|
||||||
|
case WAIT_FOR_SYNC -> "Scanning...";
|
||||||
|
case WAIT_FOR_SOURCE_ADD -> "Connecting...";
|
||||||
|
case SOURCE_ADDED -> "Listening now";
|
||||||
|
default -> "";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
void showToast(String msg) {
|
void showToast(String msg) {
|
||||||
AudioSharingUtils.toastMessage(mContext, msg);
|
AudioSharingUtils.toastMessage(mContext, msg);
|
||||||
}
|
}
|
||||||
@@ -235,13 +342,15 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
|
|||||||
mLeBroadcastAssistant.registerServiceCallBack(mExecutor, mBroadcastAssistantCallback);
|
mLeBroadcastAssistant.registerServiceCallBack(mExecutor, mBroadcastAssistantCallback);
|
||||||
mLeBroadcastAssistant.startSearchingForSources(emptyList());
|
mLeBroadcastAssistant.startSearchingForSources(emptyList());
|
||||||
|
|
||||||
// Display currently connected streams
|
// Handle QR code scan and display currently connected streams
|
||||||
var unused =
|
var unused =
|
||||||
ThreadUtils.postOnBackgroundThread(
|
ThreadUtils.postOnBackgroundThread(
|
||||||
() ->
|
() -> {
|
||||||
mAudioStreamsHelper
|
handleSourceFromQrCodeIfExists();
|
||||||
.getAllSources()
|
mAudioStreamsHelper
|
||||||
.forEach(this::handleSourceConnected));
|
.getAllConnectedSources()
|
||||||
|
.forEach(this::handleSourceConnected);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void stopScanning() {
|
private void stopScanning() {
|
||||||
@@ -256,6 +365,43 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
|
|||||||
mLeBroadcastAssistant.stopSearchingForSources();
|
mLeBroadcastAssistant.stopSearchingForSources();
|
||||||
}
|
}
|
||||||
mLeBroadcastAssistant.unregisterServiceCallBack(mBroadcastAssistantCallback);
|
mLeBroadcastAssistant.unregisterServiceCallBack(mBroadcastAssistantCallback);
|
||||||
|
if (mTimedSourceFromQrCode != null) {
|
||||||
|
mTimedSourceFromQrCode.consumed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private AudioStreamPreference addNewPreference(
|
||||||
|
BluetoothLeBroadcastReceiveState receiveState,
|
||||||
|
AudioStreamState state,
|
||||||
|
Preference.OnPreferenceClickListener onClickListener) {
|
||||||
|
var preference = AudioStreamPreference.fromReceiveState(mContext, receiveState, state);
|
||||||
|
updatePreferenceConnectionState(preference, state, onClickListener);
|
||||||
|
return preference;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AudioStreamPreference addNewPreference(
|
||||||
|
BluetoothLeBroadcastMetadata metadata,
|
||||||
|
AudioStreamState state,
|
||||||
|
Preference.OnPreferenceClickListener onClickListener) {
|
||||||
|
var preference = AudioStreamPreference.fromMetadata(mContext, metadata, state);
|
||||||
|
updatePreferenceConnectionState(preference, state, onClickListener);
|
||||||
|
return preference;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updatePreferenceConnectionState(
|
||||||
|
AudioStreamPreference preference,
|
||||||
|
AudioStreamState state,
|
||||||
|
Preference.OnPreferenceClickListener onClickListener) {
|
||||||
|
ThreadUtils.postOnMainThread(
|
||||||
|
() -> {
|
||||||
|
preference.setIsConnected(
|
||||||
|
state == AudioStreamState.SOURCE_ADDED,
|
||||||
|
getPreferenceSummary(state),
|
||||||
|
onClickListener);
|
||||||
|
if (mCategoryPreference != null) {
|
||||||
|
mCategoryPreference.addPreference(preference);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean launchDetailFragment(int broadcastId) {
|
private boolean launchDetailFragment(int broadcastId) {
|
||||||
@@ -282,7 +428,8 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void launchPasswordDialog(BluetoothLeBroadcastMetadata source, Preference preference) {
|
private void launchPasswordDialog(
|
||||||
|
BluetoothLeBroadcastMetadata source, AudioStreamPreference preference) {
|
||||||
View layout =
|
View layout =
|
||||||
LayoutInflater.from(mContext)
|
LayoutInflater.from(mContext)
|
||||||
.inflate(R.layout.bluetooth_find_broadcast_password_dialog, null);
|
.inflate(R.layout.bluetooth_find_broadcast_password_dialog, null);
|
||||||
@@ -307,8 +454,49 @@ public class AudioStreamsProgressCategoryController extends BasePreferenceContro
|
|||||||
.setBroadcastCode(
|
.setBroadcastCode(
|
||||||
code.getBytes(StandardCharsets.UTF_8))
|
code.getBytes(StandardCharsets.UTF_8))
|
||||||
.build());
|
.build());
|
||||||
|
preference.setAudioStreamState(
|
||||||
|
AudioStreamState.WAIT_FOR_SOURCE_ADD);
|
||||||
|
updatePreferenceConnectionState(
|
||||||
|
preference, AudioStreamState.WAIT_FOR_SOURCE_ADD, null);
|
||||||
})
|
})
|
||||||
.create();
|
.create();
|
||||||
alertDialog.show();
|
alertDialog.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class TimedSourceFromQrCode {
|
||||||
|
private static final int WAIT_FOR_SYNC_TIMEOUT_MILLIS = 15000;
|
||||||
|
private final CountDownTimer mTimer;
|
||||||
|
private BluetoothLeBroadcastMetadata mSourceFromQrCode;
|
||||||
|
|
||||||
|
private TimedSourceFromQrCode(
|
||||||
|
Context context,
|
||||||
|
BluetoothLeBroadcastMetadata sourceFromQrCode,
|
||||||
|
Runnable timeoutAction) {
|
||||||
|
mSourceFromQrCode = sourceFromQrCode;
|
||||||
|
mTimer =
|
||||||
|
new CountDownTimer(WAIT_FOR_SYNC_TIMEOUT_MILLIS, 1000) {
|
||||||
|
@Override
|
||||||
|
public void onTick(long millisUntilFinished) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFinish() {
|
||||||
|
timeoutAction.run();
|
||||||
|
AudioSharingUtils.toastMessage(context, "Audio steam isn't available");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void waitForConsume() {
|
||||||
|
mTimer.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void consumed() {
|
||||||
|
mTimer.cancel();
|
||||||
|
mSourceFromQrCode = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BluetoothLeBroadcastMetadata get() {
|
||||||
|
return mSourceFromQrCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user