Merge "Synchronize group-related variables"
diff --git a/android/app/Android.bp b/android/app/Android.bp
index dc76c5a..8cbdac8 100644
--- a/android/app/Android.bp
+++ b/android/app/Android.bp
@@ -137,8 +137,8 @@
genrule {
name: "statslog-bluetooth-java-gen",
tools: ["stats-log-api-gen"],
- cmd: "$(location stats-log-api-gen) --java $(out) --module bluetooth"
- + " --javaPackage com.android.bluetooth --javaClass BluetoothStatsLog"
- + " --minApiLevel 32",
+ cmd: "$(location stats-log-api-gen) --java $(out) --module bluetooth" +
+ " --javaPackage com.android.bluetooth --javaClass BluetoothStatsLog" +
+ " --minApiLevel 33",
out: ["com/android/bluetooth/BluetoothStatsLog.java"],
}
diff --git a/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java b/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java
index 81b3911..12fdd62 100644
--- a/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java
+++ b/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java
@@ -1855,6 +1855,10 @@
public void connect(BluetoothDevice device, AttributionSource source,
SynchronousResultReceiver receiver) {
try {
+ Objects.requireNonNull(device, "device cannot be null");
+ Objects.requireNonNull(source, "source cannot be null");
+ Objects.requireNonNull(receiver, "receiver cannot be null");
+
LeAudioService service = getService(source);
boolean defaultValue = false;
if (service != null) {
@@ -1870,6 +1874,10 @@
public void disconnect(BluetoothDevice device, AttributionSource source,
SynchronousResultReceiver receiver) {
try {
+ Objects.requireNonNull(device, "device cannot be null");
+ Objects.requireNonNull(source, "source cannot be null");
+ Objects.requireNonNull(receiver, "receiver cannot be null");
+
LeAudioService service = getService(source);
boolean defaultValue = false;
if (service != null) {
@@ -1885,6 +1893,9 @@
public void getConnectedDevices(AttributionSource source,
SynchronousResultReceiver receiver) {
try {
+ Objects.requireNonNull(source, "source cannot be null");
+ Objects.requireNonNull(receiver, "receiver cannot be null");
+
LeAudioService service = getService(source);
List<BluetoothDevice> defaultValue = new ArrayList<>(0);
if (service != null) {
@@ -1900,6 +1911,9 @@
public void getConnectedGroupLeadDevice(int groupId, AttributionSource source,
SynchronousResultReceiver receiver) {
try {
+ Objects.requireNonNull(source, "source cannot be null");
+ Objects.requireNonNull(receiver, "receiver cannot be null");
+
LeAudioService service = getService(source);
BluetoothDevice defaultValue = null;
if (service != null) {
@@ -1915,6 +1929,9 @@
public void getDevicesMatchingConnectionStates(int[] states,
AttributionSource source, SynchronousResultReceiver receiver) {
try {
+ Objects.requireNonNull(source, "source cannot be null");
+ Objects.requireNonNull(receiver, "receiver cannot be null");
+
LeAudioService service = getService(source);
List<BluetoothDevice> defaultValue = new ArrayList<>(0);
if (service != null) {
@@ -1930,6 +1947,10 @@
public void getConnectionState(BluetoothDevice device, AttributionSource source,
SynchronousResultReceiver receiver) {
try {
+ Objects.requireNonNull(device, "device cannot be null");
+ Objects.requireNonNull(source, "source cannot be null");
+ Objects.requireNonNull(receiver, "receiver cannot be null");
+
LeAudioService service = getService(source);
int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
if (service != null) {
@@ -1945,6 +1966,10 @@
public void setActiveDevice(BluetoothDevice device, AttributionSource source,
SynchronousResultReceiver receiver) {
try {
+ Objects.requireNonNull(device, "device cannot be null");
+ Objects.requireNonNull(source, "source cannot be null");
+ Objects.requireNonNull(receiver, "receiver cannot be null");
+
LeAudioService service = getService(source);
boolean defaultValue = false;
if (service != null) {
@@ -1959,6 +1984,9 @@
@Override
public void getActiveDevices(AttributionSource source, SynchronousResultReceiver receiver) {
try {
+ Objects.requireNonNull(source, "source cannot be null");
+ Objects.requireNonNull(receiver, "receiver cannot be null");
+
LeAudioService service = getService(source);
List<BluetoothDevice> defaultValue = new ArrayList<>();
if (service != null) {
@@ -1974,6 +2002,10 @@
public void getAudioLocation(BluetoothDevice device, AttributionSource source,
SynchronousResultReceiver receiver) {
try {
+ Objects.requireNonNull(device, "device cannot be null");
+ Objects.requireNonNull(source, "source cannot be null");
+ Objects.requireNonNull(receiver, "receiver cannot be null");
+
LeAudioService service = getService(source);
int defaultValue = BluetoothLeAudio.AUDIO_LOCATION_INVALID;
if (service != null) {
@@ -1988,6 +2020,10 @@
@Override
public void setConnectionPolicy(BluetoothDevice device, int connectionPolicy,
AttributionSource source, SynchronousResultReceiver receiver) {
+ Objects.requireNonNull(device, "device cannot be null");
+ Objects.requireNonNull(source, "source cannot be null");
+ Objects.requireNonNull(receiver, "receiver cannot be null");
+
try {
LeAudioService service = getService(source);
boolean defaultValue = false;
@@ -2004,11 +2040,17 @@
public void getConnectionPolicy(BluetoothDevice device, AttributionSource source,
SynchronousResultReceiver receiver) {
try {
+ Objects.requireNonNull(device, "device cannot be null");
+ Objects.requireNonNull(source, "source cannot be null");
+ Objects.requireNonNull(receiver, "receiver cannot be null");
+
LeAudioService service = getService(source);
int defaultValue = BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
- if (service != null) {
- defaultValue = service.getConnectionPolicy(device);
+ if (service == null) {
+ throw new IllegalStateException("service is null");
}
+ enforceBluetoothPrivilegedPermission(service);
+ defaultValue = service.getConnectionPolicy(device);
receiver.send(defaultValue);
} catch (RuntimeException e) {
receiver.propagateException(e);
@@ -2019,12 +2061,17 @@
public void getGroupId(BluetoothDevice device, AttributionSource source,
SynchronousResultReceiver receiver) {
try {
+ Objects.requireNonNull(device, "device cannot be null");
+ Objects.requireNonNull(source, "source cannot be null");
+ Objects.requireNonNull(receiver, "receiver cannot be null");
+
LeAudioService service = getService(source);
int defaultValue = LE_AUDIO_GROUP_ID_INVALID;
- if (service != null) {
-
- defaultValue = service.getGroupId(device);
+ if (service == null) {
+ throw new IllegalStateException("service is null");
}
+ enforceBluetoothPrivilegedPermission(service);
+ defaultValue = service.getGroupId(device);
receiver.send(defaultValue);
} catch (RuntimeException e) {
receiver.propagateException(e);
@@ -2035,11 +2082,17 @@
public void groupAddNode(int group_id, BluetoothDevice device,
AttributionSource source, SynchronousResultReceiver receiver) {
try {
+ Objects.requireNonNull(device, "device cannot be null");
+ Objects.requireNonNull(source, "source cannot be null");
+ Objects.requireNonNull(receiver, "receiver cannot be null");
+
LeAudioService service = getService(source);
boolean defaultValue = false;
- if (service != null) {
- defaultValue = service.groupAddNode(group_id, device);
+ if (service == null) {
+ throw new IllegalStateException("service is null");
}
+ enforceBluetoothPrivilegedPermission(service);
+ defaultValue = service.groupAddNode(group_id, device);
receiver.send(defaultValue);
} catch (RuntimeException e) {
receiver.propagateException(e);
@@ -2050,11 +2103,17 @@
public void groupRemoveNode(int groupId, BluetoothDevice device,
AttributionSource source, SynchronousResultReceiver receiver) {
try {
+ Objects.requireNonNull(device, "device cannot be null");
+ Objects.requireNonNull(source, "source cannot be null");
+ Objects.requireNonNull(receiver, "receiver cannot be null");
+
LeAudioService service = getService(source);
boolean defaultValue = false;
- if (service != null) {
- defaultValue = service.groupRemoveNode(groupId, device);
+ if (service == null) {
+ throw new IllegalStateException("service is null");
}
+ enforceBluetoothPrivilegedPermission(service);
+ defaultValue = service.groupRemoveNode(groupId, device);
receiver.send(defaultValue);
} catch (RuntimeException e) {
receiver.propagateException(e);
@@ -2065,10 +2124,15 @@
public void setVolume(int volume, AttributionSource source,
SynchronousResultReceiver receiver) {
try {
+ Objects.requireNonNull(source, "source cannot be null");
+ Objects.requireNonNull(receiver, "receiver cannot be null");
+
LeAudioService service = getService(source);
- if (service != null) {
- service.setVolume(volume);
+ if (service == null) {
+ throw new IllegalStateException("service is null");
}
+ enforceBluetoothPrivilegedPermission(service);
+ service.setVolume(volume);
receiver.send(null);
} catch (RuntimeException e) {
receiver.propagateException(e);
@@ -2078,14 +2142,17 @@
@Override
public void registerCallback(IBluetoothLeAudioCallback callback,
AttributionSource source, SynchronousResultReceiver receiver) {
- LeAudioService service = getService(source);
- if ((service == null) || (service.mLeAudioCallbacks == null)) {
- receiver.propagateException(new IllegalStateException("Service is unavailable"));
- return;
- }
-
- enforceBluetoothPrivilegedPermission(service);
try {
+ Objects.requireNonNull(callback, "callback cannot be null");
+ Objects.requireNonNull(source, "source cannot be null");
+ Objects.requireNonNull(receiver, "receiver cannot be null");
+
+ LeAudioService service = getService(source);
+ if ((service == null) || (service.mLeAudioCallbacks == null)) {
+ throw new IllegalStateException("Service is unavailable: " + service);
+ }
+
+ enforceBluetoothPrivilegedPermission(service);
service.mLeAudioCallbacks.register(callback);
receiver.send(null);
} catch (RuntimeException e) {
@@ -2096,14 +2163,18 @@
@Override
public void unregisterCallback(IBluetoothLeAudioCallback callback,
AttributionSource source, SynchronousResultReceiver receiver) {
- LeAudioService service = getService(source);
- if ((service == null) || (service.mLeAudioCallbacks == null)) {
- receiver.propagateException(new IllegalStateException("Service is unavailable"));
- return;
- }
-
- enforceBluetoothPrivilegedPermission(service);
try {
+ Objects.requireNonNull(callback, "callback cannot be null");
+ Objects.requireNonNull(source, "source cannot be null");
+ Objects.requireNonNull(receiver, "receiver cannot be null");
+
+ LeAudioService service = getService(source);
+ if ((service == null) || (service.mLeAudioCallbacks == null)) {
+ throw new IllegalStateException("Service is unavailable");
+ }
+
+ enforceBluetoothPrivilegedPermission(service);
+
service.mLeAudioCallbacks.unregister(callback);
receiver.send(null);
} catch (RuntimeException e) {
@@ -2114,14 +2185,18 @@
@Override
public void registerLeBroadcastCallback(IBluetoothLeBroadcastCallback callback,
AttributionSource source, SynchronousResultReceiver receiver) {
- LeAudioService service = getService(source);
- if ((service == null) || (service.mBroadcastCallbacks == null)) {
- receiver.propagateException(new IllegalStateException("Service is unavailable"));
- return;
- }
-
- enforceBluetoothPrivilegedPermission(service);
try {
+ Objects.requireNonNull(callback, "callback cannot be null");
+ Objects.requireNonNull(source, "source cannot be null");
+ Objects.requireNonNull(receiver, "receiver cannot be null");
+
+ LeAudioService service = getService(source);
+ if ((service == null) || (service.mBroadcastCallbacks == null)) {
+ throw new IllegalStateException("Service is unavailable");
+ }
+
+ enforceBluetoothPrivilegedPermission(service);
+
service.mBroadcastCallbacks.register(callback);
receiver.send(null);
} catch (RuntimeException e) {
@@ -2132,14 +2207,17 @@
@Override
public void unregisterLeBroadcastCallback(IBluetoothLeBroadcastCallback callback,
AttributionSource source, SynchronousResultReceiver receiver) {
- LeAudioService service = getService(source);
- if ((service == null) || (service.mBroadcastCallbacks == null)) {
- receiver.propagateException(new IllegalStateException("Service is unavailable"));
- return;
- }
-
- enforceBluetoothPrivilegedPermission(service);
try {
+ LeAudioService service = getService(source);
+ if ((service == null) || (service.mBroadcastCallbacks == null)) {
+ throw new IllegalStateException("Service is unavailable");
+ }
+
+ enforceBluetoothPrivilegedPermission(service);
+ Objects.requireNonNull(callback, "callback cannot be null");
+ Objects.requireNonNull(source, "source cannot be null");
+ Objects.requireNonNull(receiver, "receiver cannot be null");
+
service.mBroadcastCallbacks.unregister(callback);
receiver.send(null);
} catch (RuntimeException e) {
diff --git a/android/app/src/com/android/bluetooth/vc/VolumeControlService.java b/android/app/src/com/android/bluetooth/vc/VolumeControlService.java
index 87a415e..caf7d4a 100644
--- a/android/app/src/com/android/bluetooth/vc/VolumeControlService.java
+++ b/android/app/src/com/android/bluetooth/vc/VolumeControlService.java
@@ -412,6 +412,11 @@
*/
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public boolean okToConnect(BluetoothDevice device) {
+ /* Make sure device is valid */
+ if (device == null) {
+ Log.e(TAG, "okToConnect: Invalid device");
+ return false;
+ }
// Check if this is an incoming connection in Quiet mode.
if (mAdapterService.isQuietModeEnabled()) {
Log.e(TAG, "okToConnect: cannot connect to " + device + " : quiet mode enabled");
@@ -921,6 +926,10 @@
public void connect(BluetoothDevice device, AttributionSource source,
SynchronousResultReceiver receiver) {
try {
+ Objects.requireNonNull(device, "device cannot be null");
+ Objects.requireNonNull(source, "source cannot be null");
+ Objects.requireNonNull(receiver, "receiver cannot be null");
+
VolumeControlService service = getService(source);
boolean defaultValue = false;
if (service != null) {
@@ -936,6 +945,10 @@
public void disconnect(BluetoothDevice device, AttributionSource source,
SynchronousResultReceiver receiver) {
try {
+ Objects.requireNonNull(device, "device cannot be null");
+ Objects.requireNonNull(source, "source cannot be null");
+ Objects.requireNonNull(receiver, "receiver cannot be null");
+
VolumeControlService service = getService(source);
boolean defaultValue = false;
if (service != null) {
@@ -951,6 +964,9 @@
public void getConnectedDevices(AttributionSource source,
SynchronousResultReceiver receiver) {
try {
+ Objects.requireNonNull(source, "source cannot be null");
+ Objects.requireNonNull(receiver, "receiver cannot be null");
+
VolumeControlService service = getService(source);
List<BluetoothDevice> defaultValue = new ArrayList<>();
if (service != null) {
@@ -967,6 +983,9 @@
public void getDevicesMatchingConnectionStates(int[] states,
AttributionSource source, SynchronousResultReceiver receiver) {
try {
+ Objects.requireNonNull(source, "source cannot be null");
+ Objects.requireNonNull(receiver, "receiver cannot be null");
+
VolumeControlService service = getService(source);
List<BluetoothDevice> defaultValue = new ArrayList<>();
if (service != null) {
@@ -982,6 +1001,10 @@
public void getConnectionState(BluetoothDevice device, AttributionSource source,
SynchronousResultReceiver receiver) {
try {
+ Objects.requireNonNull(device, "device cannot be null");
+ Objects.requireNonNull(source, "source cannot be null");
+ Objects.requireNonNull(receiver, "receiver cannot be null");
+
VolumeControlService service = getService(source);
int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
if (service != null) {
@@ -997,6 +1020,10 @@
public void setConnectionPolicy(BluetoothDevice device, int connectionPolicy,
AttributionSource source, SynchronousResultReceiver receiver) {
try {
+ Objects.requireNonNull(device, "device cannot be null");
+ Objects.requireNonNull(source, "source cannot be null");
+ Objects.requireNonNull(receiver, "receiver cannot be null");
+
VolumeControlService service = getService(source);
boolean defaultValue = false;
if (service != null) {
@@ -1012,6 +1039,10 @@
public void getConnectionPolicy(BluetoothDevice device, AttributionSource source,
SynchronousResultReceiver receiver) {
try {
+ Objects.requireNonNull(device, "device cannot be null");
+ Objects.requireNonNull(source, "source cannot be null");
+ Objects.requireNonNull(receiver, "receiver cannot be null");
+
VolumeControlService service = getService(source);
int defaultValue = BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
if (service != null) {
@@ -1027,6 +1058,10 @@
public void isVolumeOffsetAvailable(BluetoothDevice device,
AttributionSource source, SynchronousResultReceiver receiver) {
try {
+ Objects.requireNonNull(device, "device cannot be null");
+ Objects.requireNonNull(source, "source cannot be null");
+ Objects.requireNonNull(receiver, "receiver cannot be null");
+
boolean defaultValue = false;
VolumeControlService service = getService(source);
if (service != null) {
@@ -1042,6 +1077,10 @@
public void setVolumeOffset(BluetoothDevice device, int volumeOffset,
AttributionSource source, SynchronousResultReceiver receiver) {
try {
+ Objects.requireNonNull(device, "device cannot be null");
+ Objects.requireNonNull(source, "source cannot be null");
+ Objects.requireNonNull(receiver, "receiver cannot be null");
+
VolumeControlService service = getService(source);
if (service != null) {
service.setVolumeOffset(device, volumeOffset);
@@ -1056,6 +1095,9 @@
public void setVolumeGroup(int groupId, int volume, AttributionSource source,
SynchronousResultReceiver receiver) {
try {
+ Objects.requireNonNull(source, "source cannot be null");
+ Objects.requireNonNull(receiver, "receiver cannot be null");
+
VolumeControlService service = getService(source);
if (service != null) {
service.setVolumeGroup(groupId, volume);
@@ -1069,38 +1111,44 @@
@Override
public void registerCallback(IBluetoothVolumeControlCallback callback,
AttributionSource source, SynchronousResultReceiver receiver) {
- VolumeControlService service = getService(source);
- if (service == null) {
- throw new IllegalStateException("Service is unavailable");
- }
-
- enforceBluetoothPrivilegedPermission(service);
-
try {
+ Objects.requireNonNull(callback, "callback cannot be null");
+ Objects.requireNonNull(source, "source cannot be null");
+ Objects.requireNonNull(receiver, "receiver cannot be null");
+
+ VolumeControlService service = getService(source);
+ if (service == null) {
+ throw new IllegalStateException("Service is unavailable");
+ }
+
+ enforceBluetoothPrivilegedPermission(service);
+
service.mCallbacks.register(callback);
receiver.send(null);
} catch (RuntimeException e) {
- Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
- throw new IllegalArgumentException(" Invalid callback");
+ receiver.propagateException(e);
}
}
@Override
public void unregisterCallback(IBluetoothVolumeControlCallback callback,
AttributionSource source, SynchronousResultReceiver receiver) {
- VolumeControlService service = getService(source);
- if (service == null) {
- throw new IllegalStateException("Service is unavailable");
- }
-
- enforceBluetoothPrivilegedPermission(service);
-
try {
+ Objects.requireNonNull(callback, "callback cannot be null");
+ Objects.requireNonNull(source, "source cannot be null");
+ Objects.requireNonNull(receiver, "receiver cannot be null");
+
+ VolumeControlService service = getService(source);
+ if (service == null) {
+ throw new IllegalStateException("Service is unavailable");
+ }
+
+ enforceBluetoothPrivilegedPermission(service);
+
service.mCallbacks.unregister(callback);
receiver.send(null);
} catch (RuntimeException e) {
- Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
- throw new IllegalArgumentException(" Invalid callback ");
+ receiver.propagateException(e);
}
}
}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/vc/VolumeControlServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/vc/VolumeControlServiceTest.java
index 5408207..cbf4074 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/vc/VolumeControlServiceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/vc/VolumeControlServiceTest.java
@@ -318,6 +318,10 @@
// Send a connect request
Assert.assertTrue("Connect expected to succeed", mService.connect(mDevice));
+
+ // Verify the connection state broadcast, and that we are in Connecting state
+ verifyConnectionStateIntent(TIMEOUT_MS, mDevice, BluetoothProfile.STATE_CONNECTING,
+ BluetoothProfile.STATE_DISCONNECTED);
}
/**
diff --git a/android/leaudio/app/src/main/AndroidManifest.xml b/android/leaudio/app/src/main/AndroidManifest.xml
index 996dcee..120142d 100644
--- a/android/leaudio/app/src/main/AndroidManifest.xml
+++ b/android/leaudio/app/src/main/AndroidManifest.xml
@@ -36,6 +36,11 @@
android:excludeFromRecents="true"
android:theme="@style/AppTheme.NoActionBar">
</activity>
+ <activity
+ android:name=".BroadcastScanActivity"
+ android:excludeFromRecents="true"
+ android:theme="@style/AppTheme.NoActionBar">
+ </activity>
</application>
</manifest>
diff --git a/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BluetoothProxy.java b/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BluetoothProxy.java
index 0e628ab..0b931ad 100644
--- a/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BluetoothProxy.java
+++ b/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BluetoothProxy.java
@@ -26,40 +26,45 @@
import android.os.ParcelUuid;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.core.util.Pair;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
-import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Optional;
+import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
-import com.android.bluetooth.leaudio.R;
-
public class BluetoothProxy {
private static BluetoothProxy INSTANCE;
private final Application application;
private final BluetoothAdapter bluetoothAdapter;
private BluetoothLeAudio bluetoothLeAudio = null;
private BluetoothLeBroadcast mBluetoothLeBroadcast = null;
+ private BluetoothLeBroadcastAssistant mBluetoothLeBroadcastAssistant = null;
+ private Set<BluetoothDevice> mBroadcastScanOnBehalfDevices = new HashSet<>();
private BluetoothCsipSetCoordinator bluetoothCsis = null;
private BluetoothVolumeControl bluetoothVolumeControl = null;
private BluetoothHapClient bluetoothHapClient = null;
private BluetoothProfile.ServiceListener profileListener = null;
private BluetoothHapClient.Callback hapCallback = null;
+ private OnBassEventListener mBassEventListener;
+ private OnLocalBroadcastEventListener mLocalBroadcastEventListener;
private final IntentFilter adapterIntentFilter;
+ private final IntentFilter bassIntentFilter;
private IntentFilter intentFilter;
private final ExecutorService mExecutor;
@@ -290,6 +295,9 @@
}
mBroadcastAddedMutableLive.postValue(broadcastId);
+ if (mLocalBroadcastEventListener != null) {
+ mLocalBroadcastEventListener.onBroadcastStarted(broadcastId);
+ }
}
@Override
@@ -301,6 +309,9 @@
@Override
public void onBroadcastStopped(int reason, int broadcastId) {
mBroadcastRemovedMutableLive.postValue(new Pair<>(reason, broadcastId));
+ if (mLocalBroadcastEventListener != null) {
+ mLocalBroadcastEventListener.onBroadcastStopped(broadcastId);
+ }
}
@Override
@@ -323,6 +334,9 @@
public void onBroadcastUpdated(int reason, int broadcastId) {
mBroadcastStatusMutableLive.postValue("Broadcast " + broadcastId
+ "has been updated due to reason: " + reason);
+ if (mLocalBroadcastEventListener != null) {
+ mLocalBroadcastEventListener.onBroadcastUpdated(broadcastId);
+ }
}
@Override
@@ -335,9 +349,147 @@
public void onBroadcastMetadataChanged(int broadcastId,
BluetoothLeBroadcastMetadata metadata) {
mBroadcastUpdateMutableLive.postValue(metadata);
+ if (mLocalBroadcastEventListener != null) {
+ mLocalBroadcastEventListener.onBroadcastMetadataChanged(
+ broadcastId, metadata);
+ }
}
};
+ // TODO: Add behaviors in empty methods if necessary.
+ private final 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(BluetoothLeBroadcastMetadata source) {
+ if (mBassEventListener != null) {
+ mBassEventListener.onSourceFound(source);
+ }
+ }
+
+ @Override
+ public void onSourceAdded(BluetoothDevice sink, int sourceId, int reason) {}
+
+ @Override
+ public void onSourceAddFailed(BluetoothDevice sink,
+ BluetoothLeBroadcastMetadata source, int reason) {}
+
+ @Override
+ public void onSourceModified(BluetoothDevice sink, int sourceId, int reason) {}
+
+ @Override
+ public void onSourceModifyFailed(BluetoothDevice sink, int sourceId, int reason) {}
+
+ @Override
+ public void onSourceRemoved(BluetoothDevice sink, int sourceId, int reason) {}
+
+ @Override
+ public void onSourceRemoveFailed(BluetoothDevice sink, int sourceId, int reason) {}
+
+ @Override
+ public void onReceiveStateChanged(BluetoothDevice sink, int sourceId,
+ BluetoothLeBroadcastReceiveState state) {
+ if (allLeAudioDevicesMutable.getValue() != null) {
+ Optional<LeAudioDeviceStateWrapper> valid_device_opt = allLeAudioDevicesMutable
+ .getValue().stream()
+ .filter(stateWrapper -> stateWrapper.device.getAddress().equals(
+ sink.getAddress()))
+ .findAny();
+
+ if (!valid_device_opt.isPresent())
+ return;
+
+ LeAudioDeviceStateWrapper valid_device = valid_device_opt.get();
+ LeAudioDeviceStateWrapper.BassData svc_data = valid_device.bassData;
+
+ // TODO: Is the receiver_id same with BluetoothLeBroadcastReceiveState.getSourceId()?
+ // If not, find getSourceId() usages and fix the issues.
+// rstate.receiver_id = intent.getIntExtra(
+// BluetoothBroadcastAudioScan.EXTRA_BASS_RECEIVER_ID, -1);
+ /**
+ * From "Introducing-Bluetooth-LE-Audio-book" 8.6.3.1:
+ *
+ * The Source_ID is an Acceptor generated number which is used to identify a
+ * specific set of
+ * broadcast device and BIG information. It is local to an Acceptor and used as a
+ * reference for
+ * a Broadcast Assistant. In the case of a Coordinated Set of Acceptors, such as
+ * a left and right
+ * earbud, the Source_IDs are not related and may be different, even if both are
+ * receiving the
+ * same BIS, as each Acceptor independently creates their own Source ID values
+ */
+
+ /**
+ * From BluetoothBroadcastAudioScan.EXTRA_BASS_RECEIVER_ID:
+ *
+ * Broadcast receiver's endpoint identifier.
+ */
+
+ HashMap<Integer, BluetoothLeBroadcastReceiveState> states =
+ svc_data.receiverStatesMutable.getValue();
+ if (states == null)
+ states = new HashMap<>();
+ states.put(state.getSourceId(), state);
+
+ // Use SetValue instead of PostValue() since we want to make it
+ // synchronous due to getValue() we do here as well
+ // Otherwise we could miss the update and store only the last
+ // receiver ID
+ svc_data.receiverStatesMutable.setValue(states);
+ }
+ }
+ };
+
+ private final BroadcastReceiver bassIntentReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(BluetoothLeBroadcastAssistant.ACTION_CONNECTION_STATE_CHANGED)) {
+ final BluetoothDevice device = intent.getParcelableExtra(
+ BluetoothDevice.EXTRA_DEVICE, BluetoothDevice.class);
+
+ if (allLeAudioDevicesMutable.getValue() != null) {
+ if (device != null) {
+ Optional<LeAudioDeviceStateWrapper> valid_device_opt =
+ allLeAudioDevicesMutable
+ .getValue().stream()
+ .filter(state -> state.device.getAddress().equals(
+ device.getAddress()))
+ .findAny();
+
+ if (valid_device_opt.isPresent()) {
+ LeAudioDeviceStateWrapper valid_device = valid_device_opt.get();
+ LeAudioDeviceStateWrapper.BassData svc_data = valid_device.bassData;
+
+ final int toState = intent
+ .getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
+ if (toState == BluetoothProfile.STATE_CONNECTED
+ || toState == BluetoothProfile.STATE_DISCONNECTED)
+ svc_data.isConnectedMutable.postValue(
+ toState == BluetoothProfile.STATE_CONNECTED);
+ }
+ }
+ }
+ }
+ // TODO: Remove this if unnecessary.
+// case BluetoothBroadcastAudioScan.ACTION_BASS_BROADCAST_ANNONCEMENT_AVAILABLE:
+// // FIXME: Never happen since there is no valid device with this intent
+// break;
+ }
+ };
+
private BluetoothProxy(Application application) {
this.application = application;
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
@@ -360,6 +512,10 @@
adapterIntentFilter = new IntentFilter();
adapterIntentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
application.registerReceiver(adapterIntentReceiver, adapterIntentFilter);
+
+ bassIntentFilter = new IntentFilter();
+ bassIntentFilter.addAction(BluetoothLeBroadcastAssistant.ACTION_CONNECTION_STATE_CHANGED);
+ application.registerReceiver(bassIntentReceiver, bassIntentFilter);
}
// Lazy constructing Singleton acquire method
@@ -512,6 +668,12 @@
mBluetoothLeBroadcast = (BluetoothLeBroadcast) bluetoothProfile;
mBluetoothLeBroadcast.registerCallback(mExecutor, mBroadcasterCallback);
break;
+ case BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT:
+ mBluetoothLeBroadcastAssistant = (BluetoothLeBroadcastAssistant)
+ bluetoothProfile;
+ mBluetoothLeBroadcastAssistant.registerCallback(mExecutor,
+ mBroadcastAssistantCallback);
+ break;
}
queryLeAudioDevices();
}
@@ -525,6 +687,7 @@
initVolumeControlProxy();
initHapProxy();
initLeAudioBroadcastProxy();
+ initBassProxy();
}
public void cleanupProfiles() {
@@ -535,6 +698,7 @@
cleanupVolumeControlProxy();
cleanupHapProxy();
cleanupLeAudioBroadcastProxy();
+ cleanupBassProxy();
profileListener = null;
}
@@ -605,6 +769,19 @@
}
}
+ private void initBassProxy() {
+ bluetoothAdapter.getProfileProxy(this.application, profileListener,
+ BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
+ }
+
+ private void cleanupBassProxy() {
+ if (mBluetoothLeBroadcastAssistant != null) {
+ mBluetoothLeBroadcastAssistant.unregisterCallback(mBroadcastAssistantCallback);
+ bluetoothAdapter.closeProfileProxy(BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT,
+ mBluetoothLeBroadcastAssistant);
+ }
+ }
+
private Boolean checkForEnabledBluetooth() {
Boolean current_state = bluetoothAdapter.isEnabled();
@@ -703,6 +880,21 @@
}
}
+ if (Arrays.asList(dev.getUuids() != null ? dev.getUuids() : new ParcelUuid[0])
+ .contains(ParcelUuid.fromString(
+ application.getString(R.string.svc_uuid_broadcast_audio)))) {
+ if (state_wrapper.bassData == null)
+ state_wrapper.bassData = new LeAudioDeviceStateWrapper.BassData();
+ valid_device = true;
+
+ if (mBluetoothLeBroadcastAssistant != null) {
+ boolean is_connected = mBluetoothLeBroadcastAssistant
+ .getConnectionState(dev) == BluetoothProfile.STATE_CONNECTED;
+ state_wrapper.bassData.isConnectedMutable.setValue(is_connected);
+ }
+ }
+
+
if (valid_device) validDevices.add(state_wrapper);
}
@@ -822,6 +1014,88 @@
}
}
+ public void connectBass(BluetoothDevice device, boolean connect) {
+ if (mBluetoothLeBroadcastAssistant != null) {
+ if (connect) {
+ mBluetoothLeBroadcastAssistant.setConnectionPolicy(device,
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ } else {
+ mBluetoothLeBroadcastAssistant.setConnectionPolicy(device,
+ BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+ }
+ }
+ }
+
+ public boolean scanForBroadcasts(@NonNull BluetoothDevice onBehalfDevice, boolean scan) {
+ if (mBluetoothLeBroadcastAssistant != null) {
+ // Note: startSearchingForSources() does not support scanning on behalf of
+ // a specific device - it only searches for all BASS connected devices.
+ // Therefore, we manage the list of the devices and start/stop the scanning.
+ if (scan) {
+ mBroadcastScanOnBehalfDevices.add(onBehalfDevice);
+ mBluetoothLeBroadcastAssistant.startSearchingForSources(new ArrayList<>());
+ if (mBassEventListener != null) {
+ mBassEventListener.onScanningStateChanged(true);
+ }
+ } else {
+ mBroadcastScanOnBehalfDevices.remove(onBehalfDevice);
+ if (mBroadcastScanOnBehalfDevices.isEmpty()) {
+ mBluetoothLeBroadcastAssistant.stopSearchingForSources();
+ if (mBassEventListener != null) {
+ mBassEventListener.onScanningStateChanged(false);
+ }
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ public boolean stopBroadcastObserving() {
+ if (mBluetoothLeBroadcastAssistant != null) {
+ mBroadcastScanOnBehalfDevices.clear();
+ mBluetoothLeBroadcastAssistant.stopSearchingForSources();
+ if (mBassEventListener != null) {
+ mBassEventListener.onScanningStateChanged(false);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ // TODO: Uncomment this method if necessary
+// public boolean getBroadcastReceiverState(BluetoothDevice device, int receiver_id) {
+// if (mBluetoothLeBroadcastAssistant != null) {
+// return mBluetoothLeBroadcastAssistant.getBroadcastReceiverState(device, receiver_id);
+// }
+// return false;
+// }
+
+ public boolean addBroadcastSource(BluetoothDevice sink, BluetoothLeBroadcastMetadata sourceMetadata) {
+ if (mBluetoothLeBroadcastAssistant != null) {
+ mBluetoothLeBroadcastAssistant.addSource(sink, sourceMetadata, true /* isGroupOp */);
+ return true;
+ }
+ return false;
+ }
+
+ public boolean modifyBroadcastSource(BluetoothDevice sink, int sourceId,
+ BluetoothLeBroadcastMetadata metadata) {
+ if (mBluetoothLeBroadcastAssistant != null) {
+ mBluetoothLeBroadcastAssistant.modifySource(sink, sourceId, metadata);
+ return true;
+ }
+ return false;
+ }
+
+ public boolean removeBroadcastSource(BluetoothDevice sink, int sourceId) {
+ if (mBluetoothLeBroadcastAssistant != null) {
+ mBluetoothLeBroadcastAssistant.removeSource(sink, sourceId);
+ return true;
+ }
+ return false;
+ }
+
public void setVolume(BluetoothDevice device, int volume) {
if (bluetoothLeAudio != null) {
bluetoothLeAudio.setVolume(volume);
@@ -843,7 +1117,7 @@
BluetoothProfile.CONNECTION_POLICY_ALLOWED);
} else {
bluetoothHapClient.setConnectionPolicy(device,
- BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
+ BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
}
}
}
@@ -1056,7 +1330,7 @@
return true;
}
- public List<BluetoothLeBroadcastMetadata> getAllBroadcastMetadata() {
+ public List<BluetoothLeBroadcastMetadata> getAllLocalBroadcasts() {
if (mBluetoothLeBroadcast == null) return Collections.emptyList();
return mBluetoothLeBroadcast.getAllBroadcastMetadata();
}
@@ -1086,4 +1360,27 @@
return (bluetoothAdapter
.isLeAudioBroadcastSourceSupported() == BluetoothStatusCodes.FEATURE_SUPPORTED);
}
+
+ public void setOnBassEventListener(OnBassEventListener listener) {
+ mBassEventListener = listener;
+ }
+
+ // Used by BroadcastScanViewModel
+ public interface OnBassEventListener {
+ void onSourceFound(BluetoothLeBroadcastMetadata source);
+ void onScanningStateChanged(boolean isScanning);
+ }
+
+ public void setOnLocalBroadcastEventListener(OnLocalBroadcastEventListener listener) {
+ mLocalBroadcastEventListener = listener;
+ }
+
+ // Used by BroadcastScanViewModel
+ public interface OnLocalBroadcastEventListener {
+ // TODO: Add arguments in methods
+ void onBroadcastStarted(int broadcastId);
+ void onBroadcastStopped(int broadcastId);
+ void onBroadcastUpdated(int broadcastId);
+ void onBroadcastMetadataChanged(int broadcastId, BluetoothLeBroadcastMetadata metadata);
+ }
}
diff --git a/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BroadcastItemsAdapter.java b/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BroadcastItemsAdapter.java
index 215d71b..e98e0a9 100644
--- a/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BroadcastItemsAdapter.java
+++ b/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BroadcastItemsAdapter.java
@@ -34,12 +34,10 @@
import java.util.List;
import java.util.Map;
-import com.android.bluetooth.leaudio.R;
-
public class BroadcastItemsAdapter
extends RecyclerView.Adapter<BroadcastItemsAdapter.BroadcastItemHolder> {
- private List<BluetoothLeBroadcastMetadata> mBroadcastMetadata = new ArrayList<>();
- private final Map<Integer /* broadcastId */, Boolean /* isPlaying */> mBroadcastPlayback =
+ private List<BluetoothLeBroadcastMetadata> mBroadcastMetadataList = new ArrayList<>();
+ private final Map<Integer /* broadcastId */, Boolean /* isPlaying */> mBroadcastPlaybackMap =
new HashMap<>();
private OnItemClickListener mOnItemClickListener;
@@ -57,8 +55,8 @@
@Override
public void onBindViewHolder(@NonNull BroadcastItemHolder holder, int position) {
- Integer broadcastId = (Integer) mBroadcastPlayback.keySet().toArray()[position];
- Boolean isPlaying = mBroadcastPlayback.get(broadcastId);
+ Integer broadcastId = (Integer) mBroadcastPlaybackMap.keySet().toArray()[position];
+ Boolean isPlaying = mBroadcastPlaybackMap.get(broadcastId);
// Set card color based on the playback state
if (isPlaying) {
@@ -76,33 +74,47 @@
@Override
public int getItemCount() {
- return mBroadcastPlayback.size();
+ return mBroadcastPlaybackMap.size();
}
public void updateBroadcastsMetadata(List<BluetoothLeBroadcastMetadata> broadcasts) {
- mBroadcastMetadata = broadcasts;
+ mBroadcastMetadataList = broadcasts;
notifyDataSetChanged();
}
public void updateBroadcastMetadata(BluetoothLeBroadcastMetadata broadcast) {
- mBroadcastMetadata.removeIf(bc -> (bc.getBroadcastId() == broadcast.getBroadcastId()));
- mBroadcastMetadata.add(broadcast);
+ mBroadcastMetadataList.removeIf(bc -> (bc.getBroadcastId() == broadcast.getBroadcastId()));
+ mBroadcastMetadataList.add(broadcast);
notifyDataSetChanged();
}
public void addBroadcasts(Integer broadcastId) {
- if (!mBroadcastPlayback.containsKey(broadcastId))
- mBroadcastPlayback.put(broadcastId, false);
+ if (!mBroadcastPlaybackMap.containsKey(broadcastId))
+ mBroadcastPlaybackMap.put(broadcastId, false);
}
public void removeBroadcast(Integer broadcastId) {
- mBroadcastMetadata.removeIf(bc -> (broadcastId.equals(bc.getBroadcastId())));
- mBroadcastPlayback.remove(broadcastId);
+ mBroadcastMetadataList.removeIf(bc -> (broadcastId.equals(bc.getBroadcastId())));
+ mBroadcastPlaybackMap.remove(broadcastId);
+ notifyDataSetChanged();
+ }
+
+ public void setBroadcasts(List<BluetoothLeBroadcastMetadata> broadcasts) {
+ mBroadcastMetadataList.clear();
+ mBroadcastMetadataList.addAll(broadcasts);
+
+ for (BluetoothLeBroadcastMetadata b : broadcasts) {
+ int broadcastId = b.getBroadcastId();
+ if (mBroadcastPlaybackMap.containsKey(broadcastId)) {
+ continue;
+ }
+ mBroadcastPlaybackMap.remove(broadcastId);
+ }
notifyDataSetChanged();
}
public void updateBroadcastPlayback(Integer broadcastId, boolean isPlaying) {
- mBroadcastPlayback.put(broadcastId, isPlaying);
+ mBroadcastPlaybackMap.put(broadcastId, isPlaying);
notifyDataSetChanged();
}
@@ -125,7 +137,7 @@
int position = getAdapterPosition();
if (position != RecyclerView.NO_POSITION) {
- Integer broadcastId = (Integer) mBroadcastPlayback.keySet().toArray()[position];
+ Integer broadcastId = (Integer) mBroadcastPlaybackMap.keySet().toArray()[position];
listener.onItemClick(broadcastId);
}
});
diff --git a/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BroadcastScanActivity.java b/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BroadcastScanActivity.java
new file mode 100644
index 0000000..17cf738
--- /dev/null
+++ b/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BroadcastScanActivity.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2021 HIMSA II K/S - www.himsa.com.
+ * Represented by EHIMA - www.ehima.com
+ *
+ * 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.bluetooth.leaudio;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeBroadcastMetadata;
+import android.content.Intent;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.widget.Toast;
+
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.lifecycle.ViewModelProviders;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import java.util.Objects;
+
+
+public class BroadcastScanActivity extends AppCompatActivity {
+ // Integer key used for sending/receiving receiver ID.
+ public static final String EXTRA_BASS_RECEIVER_ID = "receiver_id";
+
+ private static final int BIS_ALL = 0xFFFFFFFF;
+
+ private BluetoothDevice device;
+ private BroadcastScanViewModel mViewModel;
+ private BroadcastItemsAdapter adapter;
+ private String mLocalBluetoothAddress;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.broadcast_scan_activity);
+
+ RecyclerView recyclerView = findViewById(R.id.broadcast_recycler_view);
+ recyclerView.setLayoutManager(new LinearLayoutManager(this));
+ recyclerView.setHasFixedSize(true);
+
+ mLocalBluetoothAddress = BluetoothAdapter.getDefaultAdapter().getAddress();
+
+ adapter = new BroadcastItemsAdapter();
+ adapter.setOnItemClickListener(broadcastId -> {
+ mViewModel.scanForBroadcasts(device, false);
+
+ BluetoothLeBroadcastMetadata broadcast = null;
+ for (BluetoothLeBroadcastMetadata b : mViewModel.getAllBroadcasts().getValue()) {
+ if (Objects.equals(b.getBroadcastId(), broadcastId)) {
+ broadcast = b;
+ break;
+ }
+ }
+
+ if (broadcast == null) {
+ Toast.makeText(recyclerView.getContext(), "Matching broadcast not found."
+ + " broadcastId=" + broadcastId, Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ // TODO: Support selecting the subgroups instead of using all
+ mViewModel.addBroadcastSource(device, broadcast);
+ if (TextUtils.equals(mLocalBluetoothAddress, broadcast.getSourceDevice().getAddress())) {
+ Toast.makeText(recyclerView.getContext(), "Add local broadcast",
+ Toast.LENGTH_SHORT).show();
+ } else {
+ Toast.makeText(recyclerView.getContext(), "Add remote broadcast",
+ Toast.LENGTH_SHORT).show();
+ }
+ });
+ recyclerView.setAdapter(adapter);
+
+ mViewModel = ViewModelProviders.of(this).get(BroadcastScanViewModel.class);
+ mViewModel.getAllBroadcasts().observe(this, audioBroadcasts -> {
+ // Update Broadcast list in the adapter
+ adapter.setBroadcasts(audioBroadcasts);
+ });
+
+ Intent intent = getIntent();
+ device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+
+ mViewModel.scanForBroadcasts(device, false);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ if (mViewModel.getAllBroadcasts().getValue() != null)
+ adapter.setBroadcasts(mViewModel.getAllBroadcasts().getValue());
+
+ mViewModel.scanForBroadcasts(device, true);
+ mViewModel.refreshBroadcasts();
+ }
+
+ @Override
+ public void onBackPressed() {
+ Intent intent = new Intent(this, MainActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
+ startActivity(intent);
+ finish();
+ }
+}
diff --git a/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BroadcastScanViewModel.java b/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BroadcastScanViewModel.java
new file mode 100644
index 0000000..3e352e3
--- /dev/null
+++ b/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BroadcastScanViewModel.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2021 HIMSA II K/S - www.himsa.com.
+ * Represented by EHIMA - www.ehima.com
+ *
+ * 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.bluetooth.leaudio;
+
+import android.app.Application;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeBroadcastMetadata;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.AndroidViewModel;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public class BroadcastScanViewModel extends AndroidViewModel {
+ private final String TAG = "BroadcastScanViewModel";
+ boolean mIsActivityScanning = false;
+ BluetoothDevice mOnBehalfDevice;
+
+ // TODO: Remove these variables if they are unnecessary
+// // AddBroadcast context
+// BluetoothDevice mSetSrcTargetDevice;
+// List<BluetoothBroadcastAudioScanBaseConfig> mSetSrcConfigs;
+// boolean mSetSrcSyncPa;
+
+ BluetoothProxy mBluetooth;
+ Application mApplication;
+ private MutableLiveData<List<BluetoothLeBroadcastMetadata>> mAllBroadcasts = new MutableLiveData<>();
+ private HashMap<Integer, BluetoothLeBroadcastMetadata> mScanSessionBroadcasts = new HashMap<>();
+
+ private final BluetoothProxy.OnBassEventListener mBassEventListener =
+ new BluetoothProxy.OnBassEventListener() {
+ @Override
+ public void onSourceFound(BluetoothLeBroadcastMetadata source) {
+ mScanSessionBroadcasts.put(source.getBroadcastId(), source);
+ }
+
+ @Override
+ public void onScanningStateChanged(boolean isScanning) {
+ if (!isScanning) {
+ // Update the live broadcast list and clear scan session results
+ List<BluetoothLeBroadcastMetadata> localSessionBroadcasts =
+ mBluetooth.getAllLocalBroadcasts();
+ ArrayList<BluetoothLeBroadcastMetadata> new_arr;
+ if (localSessionBroadcasts != null) {
+ new_arr = new ArrayList<>(localSessionBroadcasts);
+ } else {
+ new_arr = new ArrayList<>();
+ }
+ new_arr.addAll(mScanSessionBroadcasts.values());
+ mAllBroadcasts.postValue(new_arr);
+
+ // Continue as long as the main activity wants
+ if (mIsActivityScanning) {
+ if (mOnBehalfDevice != null) {
+ mBluetooth.scanForBroadcasts(mOnBehalfDevice, true);
+ }
+ }
+ } else {
+ // FIXME: Clear won't work - it would auto-update the mutable and clear it as
+ // mutable uses reference to its values
+ mScanSessionBroadcasts = new HashMap<>();
+ }
+ }
+ };
+
+ private final BluetoothProxy.OnLocalBroadcastEventListener mLocalBroadcastEventListener =
+ new BluetoothProxy.OnLocalBroadcastEventListener() {
+ @Override
+ public void onBroadcastStarted(int broadcastId) {
+ // FIXME: We need a finer grain control over updating individual broadcast state
+ // and not just the entire list of broadcasts
+ refreshBroadcasts();
+ }
+
+ @Override
+ public void onBroadcastStopped(int broadcastId) {
+ refreshBroadcasts();
+ }
+
+ @Override
+ public void onBroadcastUpdated(int broadcastId) {
+ refreshBroadcasts();
+ }
+
+ @Override
+ public void onBroadcastMetadataChanged(int broadcastId,
+ BluetoothLeBroadcastMetadata metadata) {
+ refreshBroadcasts();
+ }
+ };
+
+ public BroadcastScanViewModel(@NonNull Application application) {
+ super(application);
+ mApplication = application;
+ mBluetooth = BluetoothProxy.getBluetoothProxy(application);
+
+ mBluetooth.setOnBassEventListener(mBassEventListener);
+ mBluetooth.setOnLocalBroadcastEventListener(mLocalBroadcastEventListener);
+ }
+
+ @Override
+ public void onCleared() {
+ mBluetooth.setOnBassEventListener(null);
+ mBluetooth.setOnLocalBroadcastEventListener(null);
+ }
+
+ public LiveData<List<BluetoothLeBroadcastMetadata>> getAllBroadcasts() {
+ return mAllBroadcasts;
+ }
+
+ public void scanForBroadcasts(BluetoothDevice device, boolean scan) {
+ if (device == null) {
+ Log.e(TAG, "scanForBroadcasts: device is null. Ignoring.");
+ return;
+ }
+
+ mIsActivityScanning = scan;
+ mOnBehalfDevice = scan ? device : null;
+
+ // First update the live broadcast list
+ List<BluetoothLeBroadcastMetadata> localSessionBroadcasts =
+ mBluetooth.getAllLocalBroadcasts();
+ ArrayList<BluetoothLeBroadcastMetadata> new_arr;
+ if (localSessionBroadcasts != null) {
+ new_arr = new ArrayList<>(localSessionBroadcasts);
+ } else {
+ new_arr = new ArrayList<>();
+ }
+ new_arr.addAll(mScanSessionBroadcasts.values());
+ mAllBroadcasts.postValue(new_arr);
+
+ mBluetooth.scanForBroadcasts(device, scan);
+ }
+
+ public void addBroadcastSource(BluetoothDevice sink, BluetoothLeBroadcastMetadata sourceMetadata) {
+ mBluetooth.addBroadcastSource(sink, sourceMetadata);
+ }
+
+ public void refreshBroadcasts() {
+ // Concatenate local broadcasts to the scanned broadcast list
+ List<BluetoothLeBroadcastMetadata> localSessionBroadcasts =
+ mBluetooth.getAllLocalBroadcasts();
+ ArrayList<BluetoothLeBroadcastMetadata> new_arr = new ArrayList<>(
+ localSessionBroadcasts);
+ new_arr.addAll(mScanSessionBroadcasts.values());
+ mAllBroadcasts.postValue(new_arr);
+ }
+}
diff --git a/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BroadcasterViewModel.java b/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BroadcasterViewModel.java
index 94d4948..bf22c8e 100644
--- a/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BroadcasterViewModel.java
+++ b/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BroadcasterViewModel.java
@@ -56,11 +56,11 @@
}
public List<BluetoothLeBroadcastMetadata> getAllBroadcastMetadata() {
- return mBluetooth.getAllBroadcastMetadata();
+ return mBluetooth.getAllLocalBroadcasts();
}
public int getBroadcastCount() {
- return mBluetooth.getAllBroadcastMetadata().size();
+ return mBluetooth.getAllLocalBroadcasts().size();
}
public LiveData<BluetoothLeBroadcastMetadata> getBroadcastUpdateMetadataLive() {
diff --git a/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/LeAudioDeviceStateWrapper.java b/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/LeAudioDeviceStateWrapper.java
index e948856..3183d6b 100644
--- a/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/LeAudioDeviceStateWrapper.java
+++ b/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/LeAudioDeviceStateWrapper.java
@@ -19,6 +19,8 @@
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHapPresetInfo;
+import android.bluetooth.BluetoothLeBroadcastReceiveState;
+import android.bluetooth.BluetoothLeBroadcastSubgroup;
import androidx.core.util.Pair;
import androidx.lifecycle.MutableLiveData;
@@ -100,16 +102,10 @@
public Object viewsData = null;
}
- public static class ReceiverState {
- int receiver_id;
- int state;
- }
-
public static class BassData {
- public MutableLiveData<Boolean> isValidBassDevice = new MutableLiveData<>();
public MutableLiveData<Boolean> isConnectedMutable = new MutableLiveData<>();
- public MutableLiveData<HashMap<Integer, ReceiverState>> receiverStatesMutable =
- new MutableLiveData<>();
+ public MutableLiveData<HashMap<Integer, BluetoothLeBroadcastReceiveState>>
+ receiverStatesMutable = new MutableLiveData<>();
public Object viewsData = null;
}
diff --git a/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/LeAudioRecycleViewAdapter.java b/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/LeAudioRecycleViewAdapter.java
index 6c8384b..74ae408 100644
--- a/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/LeAudioRecycleViewAdapter.java
+++ b/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/LeAudioRecycleViewAdapter.java
@@ -17,17 +17,42 @@
package com.android.bluetooth.leaudio;
+import static android.bluetooth.BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_BAD_CODE;
+import static android.bluetooth.BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_CODE_REQUIRED;
+import static android.bluetooth.BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_DECRYPTING;
+import static android.bluetooth.BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_NOT_ENCRYPTED;
+import static android.bluetooth.BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_FAILED_TO_SYNCHRONIZE;
+import static android.bluetooth.BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE;
+import static android.bluetooth.BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED;
+import static android.bluetooth.BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCINFO_REQUEST;
+
+import static com.android.bluetooth.leaudio.BroadcastScanActivity.EXTRA_BASS_RECEIVER_ID;
+
import android.animation.ObjectAnimator;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHapClient;
import android.bluetooth.BluetoothLeAudio;
+import android.bluetooth.BluetoothLeBroadcastReceiveState;
+import android.content.Intent;
import android.content.res.Configuration;
+import android.content.res.Resources;
import android.os.ParcelUuid;
+import android.text.InputFilter;
import android.text.InputType;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.*;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ImageButton;
+import android.widget.NumberPicker;
+import android.widget.SeekBar;
+import android.widget.Spinner;
+import android.widget.Switch;
+import android.widget.TextView;
+import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -39,14 +64,11 @@
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.BitSet;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
-import com.android.bluetooth.leaudio.R;
-
public class LeAudioRecycleViewAdapter
extends RecyclerView.Adapter<LeAudioRecycleViewAdapter.ViewHolder> {
private final AppCompatActivity parent;
@@ -99,6 +121,10 @@
holder.itemView.findViewById(R.id.hap_switch).setEnabled(
Arrays.asList(leAudioDeviceStateWrapper.device.getUuids()).contains(
ParcelUuid.fromString(parent.getString(R.string.svc_uuid_has))));
+
+ holder.itemView.findViewById(R.id.bass_switch)
+ .setEnabled(Arrays.asList(leAudioDeviceStateWrapper.device.getUuids())
+ .contains(ParcelUuid.fromString(parent.getString(R.string.svc_uuid_broadcast_audio))));
}
}
@@ -108,6 +134,7 @@
setVolumeControlUiStateObservers(holder, leAudioDeviceStateWrapper);
setBassStateObservers(holder, leAudioDeviceStateWrapper);
setHasStateObservers(holder, leAudioDeviceStateWrapper);
+ setBassUiStateObservers(holder, leAudioDeviceStateWrapper);
}
private void setLeAudioStateObservers(@NonNull ViewHolder holder,
@@ -626,6 +653,80 @@
}
}
+ private void setBassUiStateObservers(@NonNull ViewHolder holder, LeAudioDeviceStateWrapper leAudioDeviceStateWrapper) {
+ if (leAudioDeviceStateWrapper.bassData == null)
+ return;
+
+ ViewHolderBassPersistentData vData = (ViewHolderBassPersistentData)leAudioDeviceStateWrapper.bassData.viewsData;
+ if (vData == null)
+ return;
+
+ if (vData.selectedReceiverPositionMutable.hasObservers())
+ vData.selectedReceiverPositionMutable.removeObservers(this.parent);
+
+ vData.selectedReceiverPositionMutable.observe(this.parent, aInteger -> {
+ int receiver_id = Integer.parseInt(holder.bassReceiverIdSpinner.getItemAtPosition(aInteger).toString());
+ bassInteractionListener.onReceiverSelected(leAudioDeviceStateWrapper, receiver_id);
+
+ Map<Integer, BluetoothLeBroadcastReceiveState> states =
+ leAudioDeviceStateWrapper.bassData.receiverStatesMutable.getValue();
+
+ if (states != null) {
+ if (states.containsKey(receiver_id)) {
+ BluetoothLeBroadcastReceiveState state =
+ states.get(holder.bassReceiverIdSpinner.getSelectedItem());
+ final int paSyncState = state.getPaSyncState();
+ final int bigEncryptionState = state.getBigEncryptionState();
+
+ Resources res = this.parent.getResources();
+ String stateName = null;
+
+ // Set the icon
+ if (paSyncState == PA_SYNC_STATE_IDLE) {
+ holder.bassScanButton.setImageResource(R.drawable.ic_cast_black_24dp);
+ stateName = res.getString(R.string.broadcast_state_idle);
+ } else if (paSyncState == PA_SYNC_STATE_FAILED_TO_SYNCHRONIZE) {
+ holder.bassScanButton.setImageResource(R.drawable.ic_warning_black_24dp);
+ stateName = res.getString(R.string.broadcast_state_sync_pa_failed);
+ } else if (paSyncState == PA_SYNC_STATE_SYNCHRONIZED) {
+ switch (bigEncryptionState) {
+ case BIG_ENCRYPTION_STATE_NOT_ENCRYPTED:
+ case BIG_ENCRYPTION_STATE_DECRYPTING:
+ holder.bassScanButton.setImageResource(
+ R.drawable.ic_bluetooth_searching_black_24dp);
+ stateName = res.getString(R.string.broadcast_state_receiving_broadcast);
+ break;
+ case BIG_ENCRYPTION_STATE_CODE_REQUIRED:
+ holder.bassScanButton.setImageResource(
+ R.drawable.ic_vpn_key_black_24dp);
+ stateName = res.getString(R.string.broadcast_state_code_required);
+ break;
+ case BIG_ENCRYPTION_STATE_BAD_CODE:
+ holder.bassScanButton.setImageResource(R.drawable.ic_warning_black_24dp);
+ stateName = res.getString(R.string.broadcast_state_code_invalid);
+ break;
+ }
+ }
+
+ // TODO: Seems no appropriate state matching exists for RECEIVER_STATE_SYNCING
+ // and RECEIVER_STATE_SET_SOURCE_FAILED.
+ // What does "receiver source configuration has failed" mean?
+// else if (state == BluetoothBroadcastAudioScan.RECEIVER_STATE_SYNCING) {
+// holder.bassScanButton.setImageResource(R.drawable.ic_bluetooth_dots_black);
+// stateName = res.getString(R.string.broadcast_state_syncing);
+// }
+// } else if (state == BluetoothBroadcastAudioScan.RECEIVER_STATE_SET_SOURCE_FAILED) {
+// holder.bassScanButton.setImageResource(R.drawable.ic_refresh_black_24dp);
+// stateName = res.getString(R.string.broadcast_state_set_source_failed);
+// }
+
+ holder.bassReceiverStateText.setText(
+ stateName != null ? stateName : res.getString(R.string.unknown));
+ }
+ }
+ });
+ }
+
@Override
public long getItemId(int position) {
return devices.get(position).device.getAddress().hashCode();
@@ -766,21 +867,6 @@
int output_id, String description);
}
- public interface OnBassInteractionListener {
- void onConnectClick(LeAudioDeviceStateWrapper leAudioDeviceStateWrapper);
-
- void onDisconnectClick(LeAudioDeviceStateWrapper leAudioDeviceStateWrapper);
-
- void onReceiverSelected(LeAudioDeviceStateWrapper leAudioDeviceStateWrapper,
- int receiver_id);
-
- void onStopSyncReq(BluetoothDevice device, int receiver_id);
-
- void onRemoveSourceReq(BluetoothDevice device, int receiver_id);
-
- void onStopObserving();
- }
-
public interface OnHapInteractionListener {
void onConnectClick(LeAudioDeviceStateWrapper leAudioDeviceStateWrapper);
@@ -801,6 +887,22 @@
void onPreviousGroupPresetClicked(BluetoothDevice device);
}
+ public interface OnBassInteractionListener {
+ void onConnectClick(LeAudioDeviceStateWrapper leAudioDeviceStateWrapper);
+
+ void onDisconnectClick(LeAudioDeviceStateWrapper leAudioDeviceStateWrapper);
+
+ void onReceiverSelected(LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, int receiver_id);
+
+ void onBroadcastCodeEntered(BluetoothDevice device, int receiver_id, byte[] broadcast_code);
+
+ void onStopSyncReq(BluetoothDevice device, int receiver_id);
+
+ void onRemoveSourceReq(BluetoothDevice device, int receiver_id);
+
+ void onStopObserving();
+ }
+
public class ViewHolder extends RecyclerView.ViewHolder {
private final TextView deviceName;
@@ -880,6 +982,7 @@
private Switch bassConnectionSwitch;
private Spinner bassReceiverIdSpinner;
private TextView bassReceiverStateText;
+ private ImageButton bassScanButton;
public ViewHolder(@NonNull View itemView) {
super(itemView);
@@ -888,6 +991,7 @@
SetupLeAudioView(itemView);
setupVcView(itemView);
setupHapView(itemView);
+ setupBassView(itemView);
// Notify viewmodel via parent's click listener
itemView.setOnClickListener(view -> {
@@ -1606,6 +1710,147 @@
}
});
}
+
+ private void setupBassView(@NonNull View itemView) {
+ bassConnectionSwitch = itemView.findViewById(R.id.bass_switch);
+ bassConnectionSwitch.setActivated(true);
+ bassReceiverIdSpinner = itemView.findViewById(R.id.num_receiver_spinner);
+ bassReceiverStateText = itemView.findViewById(R.id.receiver_state_text);
+ bassScanButton = itemView.findViewById(R.id.broadcast_button);
+
+ bassConnectionSwitch.setOnCheckedChangeListener((compoundButton, b) -> {
+ if (!compoundButton.isActivated())
+ return;
+
+ if (bassInteractionListener != null) {
+ if (b)
+ bassInteractionListener.onConnectClick(
+ devices.get(ViewHolder.this.getAdapterPosition()));
+ else
+ bassInteractionListener.onDisconnectClick(
+ devices.get(ViewHolder.this.getAdapterPosition()));
+ }
+ });
+
+ bassReceiverIdSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView<?> adapterView, View view, int position, long l) {
+ LeAudioDeviceStateWrapper device = devices.get(ViewHolder.this.getAdapterPosition());
+ ((ViewHolderBassPersistentData) device.bassData.viewsData).selectedReceiverPositionMutable.setValue(position);
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> adapterView) {
+ // Nothing to do here
+ }
+ });
+
+ bassScanButton.setOnClickListener(view -> {
+ Resources res = view.getResources();
+
+ // TODO: Do not sync on the string value, but instead sync on the actual state value.
+ if (bassReceiverStateText.getText().equals(res.getString(R.string.broadcast_state_idle))) {
+ AlertDialog.Builder alert = new AlertDialog.Builder(itemView.getContext());
+ alert.setTitle("Scan and add a source or remove the currently set one.");
+
+ BluetoothDevice device = devices.get(ViewHolder.this.getAdapterPosition()).device;
+ int receiver_id = Integer.parseInt(bassReceiverIdSpinner.getSelectedItem().toString());
+
+ alert.setPositiveButton("Scan", (dialog, whichButton) -> {
+ // Scan for new announcements
+ Intent intent = new Intent(this.itemView.getContext(), BroadcastScanActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
+ intent.putExtra(EXTRA_BASS_RECEIVER_ID, receiver_id);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, devices.get(ViewHolder.this.getAdapterPosition()).device);
+ parent.startActivityForResult(intent, 666);
+ });
+ alert.setNeutralButton("Cancel", (dialog, whichButton) -> {
+ // Do nothing
+ });
+ alert.setNegativeButton("Remove", (dialog, whichButton) -> {
+ bassInteractionListener.onRemoveSourceReq(device, receiver_id);
+ });
+ alert.show();
+
+ } else if (bassReceiverStateText.getText().equals(res.getString(R.string.broadcast_state_code_required))) {
+ AlertDialog.Builder alert = new AlertDialog.Builder(itemView.getContext());
+ alert.setTitle("Please enter broadcast encryption code...");
+ EditText pass_input_view = new EditText(itemView.getContext());
+ pass_input_view.setFilters(new InputFilter[] { new InputFilter.LengthFilter(16) });
+ alert.setView(pass_input_view);
+
+ BluetoothDevice device = devices.get(ViewHolder.this.getAdapterPosition()).device;
+ int receiver_id = Integer.parseInt(bassReceiverIdSpinner.getSelectedItem().toString());
+
+ alert.setPositiveButton("Set", (dialog, whichButton) -> {
+ byte[] code = pass_input_view.getText().toString().getBytes();
+ bassInteractionListener.onBroadcastCodeEntered(device, receiver_id, code);
+ });
+ alert.setNegativeButton("Cancel", (dialog, whichButton) -> {
+ // Do nothing
+ });
+ alert.show();
+
+ } else if (bassReceiverStateText.getText().equals(res.getString(R.string.broadcast_state_receiving_broadcast))) {
+ AlertDialog.Builder alert = new AlertDialog.Builder(itemView.getContext());
+ alert.setTitle("Stop the synchronization?");
+
+ BluetoothDevice device = devices.get(ViewHolder.this.getAdapterPosition()).device;
+ int receiver_id = Integer.parseInt(bassReceiverIdSpinner.getSelectedItem().toString());
+
+ alert.setPositiveButton("Yes", (dialog, whichButton) -> {
+ bassInteractionListener.onRemoveSourceReq(device, receiver_id);
+ });
+ // FIXME: To modify source we need the valid broadcaster_id context so we should start scan here again
+ // alert.setNeutralButton("Modify", (dialog, whichButton) -> {
+ // // TODO: Open the scan dialog to get the broadcast_id
+ // // bassInteractionListener.onStopSyncReq(device, receiver_id, broadcast_id);
+ // });
+ alert.setNegativeButton("No", (dialog, whichButton) -> {
+ // Do nothing
+ });
+ alert.show();
+
+ } else if (bassReceiverStateText.getText().equals(res.getString(R.string.broadcast_state_set_source_failed))
+ || bassReceiverStateText.getText().equals(res.getString(R.string.broadcast_state_sync_pa_failed))) {
+ AlertDialog.Builder alert = new AlertDialog.Builder(itemView.getContext());
+ alert.setTitle("Retry broadcast audio announcement scan?");
+
+ alert.setPositiveButton("Yes", (dialog, whichButton) -> {
+ // Scan for new announcements
+ Intent intent = new Intent(view.getContext(), BroadcastScanActivity.class);
+ int receiver_id = Integer.parseInt(bassReceiverIdSpinner.getSelectedItem().toString());
+ intent.putExtra(EXTRA_BASS_RECEIVER_ID, receiver_id);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, devices.get(ViewHolder.this.getAdapterPosition()).device);
+ parent.startActivityForResult(intent, 666);
+ });
+ alert.setNegativeButton("No", (dialog, whichButton) -> {
+ // Do nothing
+ });
+ alert.show();
+
+ } else if (bassReceiverStateText.getText().equals(res.getString(R.string.broadcast_state_syncing))) {
+ AlertDialog.Builder alert = new AlertDialog.Builder(itemView.getContext());
+ alert.setTitle("Stop the synchronization?");
+
+ BluetoothDevice device = devices.get(ViewHolder.this.getAdapterPosition()).device;
+ int receiver_id = Integer.parseInt(bassReceiverIdSpinner.getSelectedItem().toString());
+
+ alert.setPositiveButton("Yes", (dialog, whichButton) -> {
+ bassInteractionListener.onRemoveSourceReq(device, receiver_id);
+ });
+ // FIXME: To modify source we need the valid broadcaster_id context so we should start scan here again
+ // alert.setNeutralButton("Modify", (dialog, whichButton) -> {
+ // // TODO: Open the scan dialog to get the broadcast_id
+ // // bassInteractionListener.onStopSyncReq(device, receiver_id, broadcast_id);
+ // });
+ alert.setNegativeButton("No", (dialog, whichButton) -> {
+ // Do nothing
+ });
+ alert.show();
+ }
+ });
+ }
}
private class ViewHolderVcPersistentData {
diff --git a/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/LeAudioViewModel.java b/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/LeAudioViewModel.java
index 3d6bf34..ae31538 100644
--- a/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/LeAudioViewModel.java
+++ b/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/LeAudioViewModel.java
@@ -19,6 +19,7 @@
import android.app.Application;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeBroadcastMetadata;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
@@ -115,4 +116,37 @@
public boolean isLeAudioBroadcastSourceSupported() {
return bluetoothProxy.isLeAudioBroadcastSourceSupported();
}
+
+ public void connectBass(BluetoothDevice sink, boolean connect) {
+ bluetoothProxy.connectBass(sink, connect);
+ }
+
+ public boolean stopBroadcastObserving() {
+ return bluetoothProxy.stopBroadcastObserving();
+ }
+
+ // TODO: Uncomment this method if necessary
+// public boolean getBroadcastReceiverState(BluetoothDevice device, int receiver_id) {
+// return bluetoothProxy.getBroadcastReceiverState(device, receiver_id);
+// }
+
+ // TODO: Uncomment this method if necessary
+// public boolean modifyBroadcastSource(BluetoothDevice device, int receiver_id, boolean sync_pa,
+// List<BluetoothBroadcastAudioScanBaseConfig> configs) {
+// return bluetoothProxy.modifyBroadcastSource(device, receiver_id, sync_pa, configs);
+// }
+
+ public boolean removeBroadcastSource(BluetoothDevice sink, int receiver_id) {
+ // TODO: Find source ID from receiver_id. What is receiver_id?
+ int sourceId = 0;
+ return bluetoothProxy.removeBroadcastSource(sink, sourceId);
+ }
+
+ public boolean setBroadcastCode(BluetoothDevice sink, int receiver_id, byte[] bcast_code) {
+ // TODO: Find source ID from receiver_id. What is receiver_id?
+ // TODO: Build BluetoothLeBroadcastMetadata with the new bcast_code.
+ int sourceId = 0;
+ BluetoothLeBroadcastMetadata metadata = null;
+ return bluetoothProxy.modifyBroadcastSource(sink, sourceId, metadata);
+ }
}
diff --git a/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/MainActivity.java b/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/MainActivity.java
index 3341007..fa7e8c4 100644
--- a/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/MainActivity.java
+++ b/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/MainActivity.java
@@ -136,6 +136,21 @@
Intent intent = null;
switch (item.getItemId()) {
+ case R.id.action_scan:
+ intent = new Intent(MainActivity.this, BroadcastScanActivity.class);
+ // TODO: Why does this pass no information?
+ //intent.putExtra(BluetoothBroadcastAudioScan.EXTRA_BASS_RECEIVER_ID, 0);
+
+ // TODO: Change BluetoothAdapter.getDefaultAdapter() usages into BluetoothManager#getAdapter().
+ BluetoothAdapter mAdapter = BluetoothAdapter.getDefaultAdapter();
+
+ // What does this fake address mean?
+ byte[] address = {(byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xff};
+ BluetoothDevice dev = mAdapter.getRemoteDevice(address);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, dev);
+ startActivity(intent);
+ return true;
+
case R.id.action_broadcast:
if (leAudioViewModel.getBluetoothEnabledLive().getValue() == null
|| !leAudioViewModel.getBluetoothEnabledLive().getValue()) {
@@ -168,6 +183,9 @@
Toast.makeText(MainActivity.this, message + "(" + resultCode + ")",
Toast.LENGTH_SHORT).show();
}
+
+ // TODO: Depending on the resultCode we should either stop the sync or try the PAST
+ leAudioViewModel.stopBroadcastObserving();
}
}
@@ -548,12 +566,72 @@
.show();
}
});
+
+ recyclerViewAdapter.setOnBassInteractionListener(
+ new LeAudioRecycleViewAdapter.OnBassInteractionListener() {
+ @Override
+ public void onConnectClick(
+ LeAudioDeviceStateWrapper leAudioDeviceStateWrapper) {
+ Toast.makeText(MainActivity.this,
+ "Connecting BASS to " + leAudioDeviceStateWrapper.device.toString(),
+ Toast.LENGTH_SHORT).show();
+ leAudioViewModel.connectBass(leAudioDeviceStateWrapper.device, true);
+ }
+
+ @Override
+ public void onDisconnectClick(
+ LeAudioDeviceStateWrapper leAudioDeviceStateWrapper) {
+ Toast.makeText(MainActivity.this,
+ "Disconnecting BASS from "
+ + leAudioDeviceStateWrapper.device.toString(),
+ Toast.LENGTH_SHORT).show();
+ leAudioViewModel.connectBass(leAudioDeviceStateWrapper.device, false);
+ }
+
+ @Override
+ public void onReceiverSelected(
+ LeAudioDeviceStateWrapper leAudioDeviceStateWrapper, int receiver_id) {
+ // Do nothing here, the UI is updated elsewhere and we already have the
+ // latest state value as well
+ }
+
+ @Override
+ public void onBroadcastCodeEntered(BluetoothDevice device, int receiver_id,
+ byte[] broadcast_code) {
+ leAudioViewModel.setBroadcastCode(device, receiver_id, broadcast_code);
+ }
+
+ @Override
+ public void onStopSyncReq(BluetoothDevice device, int receiver_id) {
+ // TODO: When is onStopSyncReq called? and what does below code do?
+
+// List<BluetoothBroadcastAudioScanBaseConfig> configs = new ArrayList<>();
+// // JT@CC: How come you can call this with null metadata when the
+// // constructor has the @Nonull annotation for the param?
+// BluetoothBroadcastAudioScanBaseConfig stop_config =
+// new BluetoothBroadcastAudioScanBaseConfig(0, new byte[] {});
+// configs.add(stop_config);
+//
+// leAudioViewModel.modifyBroadcastSource(device, receiver_id, false, configs);
+ }
+
+ @Override
+ public void onRemoveSourceReq(BluetoothDevice device, int receiver_id) {
+ leAudioViewModel.removeBroadcastSource(device, receiver_id);
+ }
+
+ @Override
+ public void onStopObserving() {
+ leAudioViewModel.stopBroadcastObserving();
+ }
+ });
}
private void cleanupViewsProfileUiEventListeners() {
recyclerViewAdapter.setOnLeAudioInteractionListener(null);
recyclerViewAdapter.setOnVolumeControlInteractionListener(null);
recyclerViewAdapter.setOnHapInteractionListener(null);
+ recyclerViewAdapter.setOnBassInteractionListener(null);
}
// This sets the initial values and set up the observers
diff --git a/android/leaudio/app/src/main/res/layout/broadcast_scan_activity.xml b/android/leaudio/app/src/main/res/layout/broadcast_scan_activity.xml
new file mode 100644
index 0000000..0d66e0a
--- /dev/null
+++ b/android/leaudio/app/src/main/res/layout/broadcast_scan_activity.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:context=".BroadcastScanActivity">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/scan_status_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="8dp"
+ android:layout_marginTop="8dp"
+ android:text="Scanning for broadcasts..." />
+
+ <ProgressBar
+ android:id="@+id/progressBar"
+ style="?android:attr/progressBarStyleHorizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:indeterminate="true"
+ android:indeterminateBehavior="cycle"
+ android:indeterminateOnly="true" />
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/broadcast_recycler_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:listitem="@layout/broadcast_item" />
+ </LinearLayout>
+
+</androidx.coordinatorlayout.widget.CoordinatorLayout>
diff --git a/android/leaudio/app/src/main/res/values/strings.xml b/android/leaudio/app/src/main/res/values/donottranslate_strings.xml
similarity index 91%
rename from android/leaudio/app/src/main/res/values/strings.xml
rename to android/leaudio/app/src/main/res/values/donottranslate_strings.xml
index 45d0add..0b7a060 100644
--- a/android/leaudio/app/src/main/res/values/strings.xml
+++ b/android/leaudio/app/src/main/res/values/donottranslate_strings.xml
@@ -79,6 +79,13 @@
</string-array>
<string name="group_locked">LOCKED</string>
<string name="group_unlocked">UNLOCKED</string>
+ <string name="broadcast_state_idle">IDLE</string>
+ <string name="broadcast_state_set_source_failed">SET_SOURCE_FAILED</string>
+ <string name="broadcast_state_syncing">SYNCING</string>
+ <string name="broadcast_state_sync_pa_failed">SYNC_PA_FAILED</string>
+ <string name="broadcast_state_code_required">BROADCAST_CODE_REQUIRED</string>
+ <string name="broadcast_state_code_invalid">BROADCAST_CODE_INVALID</string>
+ <string name="broadcast_state_receiving_broadcast">RECEIVING_BROADCAST</string>
<string-array name="tbs_call_states">
<item>Incoming</item>
<item>Dialing</item>
diff --git a/framework/java/android/bluetooth/BluetoothHapClient.java b/framework/java/android/bluetooth/BluetoothHapClient.java
index 5416d73..1d51670 100644
--- a/framework/java/android/bluetooth/BluetoothHapClient.java
+++ b/framework/java/android/bluetooth/BluetoothHapClient.java
@@ -644,6 +644,7 @@
public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
@ConnectionPolicy int connectionPolicy) {
if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
+ Objects.requireNonNull(device, "BluetoothDevice cannot be null");
final IBluetoothHapClient service = getService();
final boolean defaultValue = false;
if (service == null) {
@@ -673,7 +674,8 @@
* {@link #CONNECTION_POLICY_UNKNOWN}
*
* @param device Bluetooth device
- * @return connection policy of the device
+ * @return connection policy of the device or {@link #CONNECTION_POLICY_FORBIDDEN} if device is
+ * null
* @hide
*/
@SystemApi
diff --git a/service/java/com/android/server/bluetooth/BluetoothManagerService.java b/service/java/com/android/server/bluetooth/BluetoothManagerService.java
index bd6dfc9..1bae07d 100644
--- a/service/java/com/android/server/bluetooth/BluetoothManagerService.java
+++ b/service/java/com/android/server/bluetooth/BluetoothManagerService.java
@@ -1067,7 +1067,8 @@
public boolean enableBle(AttributionSource attributionSource, IBinder token)
throws RemoteException {
final String packageName = attributionSource.getPackageName();
- if (!checkBluetoothPermissions(attributionSource, "enableBle", false)) {
+ if (!checkBluetoothPermissions(attributionSource, "enableBle", false)
+ || isAirplaneModeOn()) {
if (DBG) {
Slog.d(TAG, "enableBle(): bluetooth disallowed");
}
diff --git a/system/bta/dm/bta_dm_act.cc b/system/bta/dm/bta_dm_act.cc
index ba90f8a..35c528a 100644
--- a/system/bta/dm/bta_dm_act.cc
+++ b/system/bta/dm/bta_dm_act.cc
@@ -1793,7 +1793,7 @@
(tBTA_DM_SDP_RESULT*)osi_malloc(sizeof(tBTA_DM_SDP_RESULT));
p_msg->hdr.event = BTA_DM_SDP_RESULT_EVT;
- p_msg->sdp_result = static_cast<uint16_t>(sdp_status);
+ p_msg->sdp_result = sdp_status;
bta_sys_sendmsg(p_msg);
}
diff --git a/system/bta/dm/bta_dm_int.h b/system/bta/dm/bta_dm_int.h
index 637c22a..fc5b9bc 100644
--- a/system/bta/dm/bta_dm_int.h
+++ b/system/bta/dm/bta_dm_int.h
@@ -133,7 +133,7 @@
/* data type for BTA_DM_SDP_RESULT_EVT */
typedef struct {
BT_HDR_RIGID hdr;
- uint16_t sdp_result;
+ tSDP_RESULT sdp_result;
} tBTA_DM_SDP_RESULT;
typedef struct {
diff --git a/system/bta/le_audio/client.cc b/system/bta/le_audio/client.cc
index b4bf14d..fe0a4d0 100644
--- a/system/bta/le_audio/client.cc
+++ b/system/bta/le_audio/client.cc
@@ -299,24 +299,32 @@
return;
}
- /* Releasement didn't finished in time */
- if (group->GetTargetState() == AseState::BTA_LE_AUDIO_ASE_STATE_IDLE) {
- CancelStreamingRequest();
- LeAudioDevice* leAudioDevice = group->GetFirstActiveDevice();
- LOG_ASSERT(leAudioDevice)
- << __func__ << " Shouldn't be called without an active device.";
+ LOG_ERROR(
+ " State not achieved on time for group: group id %d, current state %s, "
+ "target state: %s",
+ group_id, ToString(group->GetState()).c_str(),
+ ToString(group->GetTargetState()).c_str());
+ group->SetTargetState(AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
- do {
- if (instance) instance->DisconnectDevice(leAudioDevice, true);
- leAudioDevice = group->GetNextActiveDevice(leAudioDevice);
- } while (leAudioDevice);
-
- return;
+ /* There is an issue with a setting up stream or any other operation which
+ * are gatt operations. It means peer is not responsable. Lets close ACL
+ */
+ CancelStreamingRequest();
+ LeAudioDevice* leAudioDevice = group->GetFirstActiveDevice();
+ if (leAudioDevice == nullptr) {
+ LOG_ERROR(" Shouldn't be called without an active device.");
+ leAudioDevice = group->GetFirstDevice();
+ if (leAudioDevice == nullptr) {
+ LOG_ERROR(" Front device is null. Number of devices: %d",
+ group->Size());
+ return;
+ }
}
- LOG(ERROR) << __func__ << ", State not achieved on time, releasing ases";
-
- groupStateMachine_->StopStream(group);
+ do {
+ if (instance) instance->DisconnectDevice(leAudioDevice, true);
+ leAudioDevice = group->GetNextActiveDevice(leAudioDevice);
+ } while (leAudioDevice);
}
void UpdateContextAndLocations(LeAudioDeviceGroup* group,
@@ -3216,16 +3224,6 @@
rxUnreceivedPackets, duplicatePackets);
}
- void CompleteUserConfiguration(LeAudioDeviceGroup* group) {
- if (audio_sender_state_ == AudioState::RELEASING) {
- audio_sender_state_ = AudioState::IDLE;
- }
-
- if (audio_receiver_state_ == AudioState::RELEASING) {
- audio_receiver_state_ = AudioState::IDLE;
- }
- }
-
void HandlePendingAvailableContexts(LeAudioDeviceGroup* group) {
if (!group) return;
@@ -3272,28 +3270,20 @@
SuspendAudio();
break;
case GroupStreamStatus::CONFIGURED_BY_USER:
- CompleteUserConfiguration(group);
+ /* We are done with reconfiguration.
+ * Clean state and if Audio HAL is waiting, cancel the request
+ * so Audio HAL can Resume again.
+ */
+ CancelStreamingRequest();
+ HandlePendingAvailableContexts(group);
break;
case GroupStreamStatus::CONFIGURED_AUTONOMOUS:
/* This state is notified only when
* groups stays into CONFIGURED state after
- * STREAMING. Peer device uses cache.
- * */
- stream_setup_end_timestamp_ = 0;
- stream_setup_start_timestamp_ = 0;
-
- /* Check if stream was stopped for reconfiguration */
- if (group->IsPendingConfiguration()) {
- SuspendedForReconfiguration();
- if (!groupStateMachine_->ConfigureStream(group,
- current_context_type_)) {
- // DO SOMETHING
- }
- return;
- }
- CancelStreamingRequest();
- HandlePendingAvailableContexts(group);
- break;
+ * STREAMING. Peer device uses cache. For the moment
+ * it is handled same as IDLE
+ */
+ FALLTHROUGH;
case GroupStreamStatus::IDLE: {
stream_setup_end_timestamp_ = 0;
stream_setup_start_timestamp_ = 0;
diff --git a/system/bta/le_audio/devices.cc b/system/bta/le_audio/devices.cc
index c90db21..255ab80 100644
--- a/system/bta/le_audio/devices.cc
+++ b/system/bta/le_audio/devices.cc
@@ -60,7 +60,13 @@
void LeAudioDeviceGroup::RemoveNode(
const std::shared_ptr<LeAudioDevice>& leAudioDevice) {
+ /* Group information cleaning in the device. */
leAudioDevice->group_id_ = bluetooth::groups::kGroupUnknown;
+ for (auto ase : leAudioDevice->ases_) {
+ ase.active = false;
+ ase.cis_conn_hdl = 0;
+ }
+
leAudioDevices_.erase(
std::remove_if(
leAudioDevices_.begin(), leAudioDevices_.end(),
diff --git a/system/bta/le_audio/state_machine.cc b/system/bta/le_audio/state_machine.cc
index b897444..b27866e 100644
--- a/system/bta/le_audio/state_machine.cc
+++ b/system/bta/le_audio/state_machine.cc
@@ -1145,8 +1145,6 @@
if (cis_id == le_audio::kInvalidCisId) {
/* Get completive (to be bi-directional CIS) CIS ID for ASE */
cis_id = leAudioDevice->GetMatchingBidirectionCisId(ase);
- LOG_INFO(" Configure ase_id %d, cis_id %d, ase state %s", ase->id,
- cis_id, ToString(ase->state).c_str());
if (cis_id == le_audio::kInvalidCisId) {
/* Get next free CIS ID for group */
cis_id = group->GetFirstFreeCisId();
@@ -1158,6 +1156,9 @@
}
}
+ LOG_INFO(" Configure ase_id %d, cis_id %d, ase state %s", ase->id, cis_id,
+ ToString(ase->state).c_str());
+
ase->cis_id = cis_id;
conf.ase_id = ase->id;
diff --git a/system/gd/rust/linux/client/src/command_handler.rs b/system/gd/rust/linux/client/src/command_handler.rs
index f2221cc..266dd5f 100644
--- a/system/gd/rust/linux/client/src/command_handler.rs
+++ b/system/gd/rust/linux/client/src/command_handler.rs
@@ -437,7 +437,7 @@
return;
}
- enforce_arg_len(args, 2, "device <connect|disconnect|info> <address>", || {
+ enforce_arg_len(args, 2, "device <connect|disconnect|info|set-alias> <address>", || {
match &args[0][0..] {
"connect" => {
let device = BluetoothDevice {
@@ -519,6 +519,31 @@
)
);
}
+ "set-alias" => {
+ if args.len() < 3 {
+ println!("usage: device set-alias <address> <new-alias>");
+ return;
+ }
+ let new_alias = &args[2];
+ let device =
+ BluetoothDevice { address: String::from(&args[1]), name: String::from("") };
+ let old_alias = self
+ .context
+ .lock()
+ .unwrap()
+ .adapter_dbus
+ .as_ref()
+ .unwrap()
+ .get_remote_alias(device.clone());
+ println!("Updating alias for {}: {} -> {}", &args[1], old_alias, new_alias);
+ self.context
+ .lock()
+ .unwrap()
+ .adapter_dbus
+ .as_mut()
+ .unwrap()
+ .set_remote_alias(device.clone(), new_alias.clone());
+ }
_ => {
println!("Invalid argument '{}'", args[0]);
}
diff --git a/system/gd/rust/linux/client/src/dbus_iface.rs b/system/gd/rust/linux/client/src/dbus_iface.rs
index 300316f..a18729e 100644
--- a/system/gd/rust/linux/client/src/dbus_iface.rs
+++ b/system/gd/rust/linux/client/src/dbus_iface.rs
@@ -400,6 +400,11 @@
dbus_generated!()
}
+ #[dbus_method("SetRemoteAlias")]
+ fn set_remote_alias(&mut self, device: BluetoothDevice, new_alias: String) {
+ dbus_generated!()
+ }
+
#[dbus_method("GetRemoteClass")]
fn get_remote_class(&self, device: BluetoothDevice) -> u32 {
dbus_generated!()
diff --git a/system/gd/rust/linux/client/src/main.rs b/system/gd/rust/linux/client/src/main.rs
index 29790f1..62e5963 100644
--- a/system/gd/rust/linux/client/src/main.rs
+++ b/system/gd/rust/linux/client/src/main.rs
@@ -319,6 +319,8 @@
format!("/org/chromium/bluetooth/client/{}/bluetooth_callback", adapter);
let conn_cb_objpath: String =
format!("/org/chromium/bluetooth/client/{}/bluetooth_conn_callback", adapter);
+ let suspend_cb_objpath: String =
+ format!("/org/chromium/bluetooth/client/{}/suspend_callback", adapter);
let dbus_connection = context.lock().unwrap().dbus_connection.clone();
let dbus_crossroads = context.lock().unwrap().dbus_crossroads.clone();
@@ -348,7 +350,7 @@
// TODO(b/224606285): Implement suspend debug utils in btclient.
context.lock().unwrap().suspend_dbus.as_mut().unwrap().register_callback(Box::new(
SuspendCallback::new(
- cb_objpath,
+ suspend_cb_objpath,
dbus_connection.clone(),
dbus_crossroads.clone(),
),
diff --git a/system/gd/rust/linux/service/src/iface_bluetooth.rs b/system/gd/rust/linux/service/src/iface_bluetooth.rs
index 37769b6..10229b2 100644
--- a/system/gd/rust/linux/service/src/iface_bluetooth.rs
+++ b/system/gd/rust/linux/service/src/iface_bluetooth.rs
@@ -248,6 +248,11 @@
dbus_generated!()
}
+ #[dbus_method("SetRemoteAlias")]
+ fn set_remote_alias(&mut self, _device: BluetoothDevice, new_alias: String) {
+ dbus_generated!()
+ }
+
#[dbus_method("GetRemoteClass")]
fn get_remote_class(&self, _device: BluetoothDevice) -> u32 {
dbus_generated!()
diff --git a/system/gd/rust/linux/service/src/main.rs b/system/gd/rust/linux/service/src/main.rs
index 62b97f2..e749843 100644
--- a/system/gd/rust/linux/service/src/main.rs
+++ b/system/gd/rust/linux/service/src/main.rs
@@ -76,19 +76,6 @@
let adapter_index = get_adapter_index(&args);
- // Hold locks and initialize all interfaces.
- {
- intf.lock().unwrap().initialize(get_bt_dispatcher(tx.clone()), args);
-
- bluetooth_media.lock().unwrap().set_adapter(bluetooth.clone());
-
- let mut bluetooth = bluetooth.lock().unwrap();
- bluetooth.init_profiles();
- bluetooth.enable();
-
- bluetooth_gatt.lock().unwrap().init_profiles(tx.clone());
- }
-
topstack::get_runtime().block_on(async {
// Connect to D-Bus system bus.
let (resource, conn) = connection::new_system_sync()?;
@@ -138,7 +125,7 @@
make_object_name(adapter_index, "gatt"),
conn.clone(),
&mut cr,
- bluetooth_gatt,
+ bluetooth_gatt.clone(),
disconnect_watcher.clone(),
);
@@ -146,7 +133,7 @@
make_object_name(adapter_index, "media"),
conn.clone(),
&mut cr,
- bluetooth_media,
+ bluetooth_media.clone(),
disconnect_watcher.clone(),
);
@@ -158,6 +145,22 @@
disconnect_watcher.clone(),
);
+ // Hold locks and initialize all interfaces. This must be done AFTER DBus is
+ // initialized so DBus can properly enforce user policies.
+ {
+ intf.lock().unwrap().initialize(get_bt_dispatcher(tx.clone()), args);
+
+ bluetooth_media.lock().unwrap().set_adapter(bluetooth.clone());
+
+ let mut bluetooth = bluetooth.lock().unwrap();
+ bluetooth.init_profiles();
+ bluetooth.enable();
+
+ bluetooth_gatt.lock().unwrap().init_profiles(tx.clone());
+ }
+
+ // Start listening on DBus after exporting interfaces and initializing
+ // all bluetooth objects.
conn.start_receive(
MatchRule::new_method_call(),
Box::new(move |msg, conn| {
diff --git a/system/gd/rust/linux/stack/btif_macros/src/lib.rs b/system/gd/rust/linux/stack/btif_macros/src/lib.rs
index 549c609..4a0f0a3 100644
--- a/system/gd/rust/linux/stack/btif_macros/src/lib.rs
+++ b/system/gd/rust/linux/stack/btif_macros/src/lib.rs
@@ -13,8 +13,20 @@
use crate::proc_macro::TokenStream;
+const OUTPUT_DEBUG: bool = false;
+
fn debug_output_to_file(gen: &proc_macro2::TokenStream, filename: String) {
- let path = Path::new(filename.as_str());
+ if !OUTPUT_DEBUG {
+ return;
+ }
+
+ let filepath = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap())
+ .join(filename)
+ .to_str()
+ .unwrap()
+ .to_string();
+
+ let path = Path::new(&filepath);
let mut file = File::create(&path).unwrap();
file.write_all(gen.to_string().as_bytes()).unwrap();
}
@@ -120,7 +132,7 @@
};
// TODO: Have a simple framework to turn on/off macro-generated code debug.
- debug_output_to_file(&gen, format!("/tmp/out-{}.rs", fn_ident.to_string()));
+ debug_output_to_file(&gen, format!("out-{}.rs", fn_ident.to_string()));
gen.into()
}
diff --git a/system/gd/rust/linux/stack/src/bluetooth.rs b/system/gd/rust/linux/stack/src/bluetooth.rs
index f2a610d..3728d1a 100644
--- a/system/gd/rust/linux/stack/src/bluetooth.rs
+++ b/system/gd/rust/linux/stack/src/bluetooth.rs
@@ -131,6 +131,9 @@
/// Gets the alias of the remote device.
fn get_remote_alias(&self, device: BluetoothDevice) -> String;
+ /// Sets the alias of the remote device.
+ fn set_remote_alias(&mut self, device: BluetoothDevice, new_alias: String);
+
/// Gets the class of the remote device.
fn get_remote_class(&self, device: BluetoothDevice) -> u32;
@@ -399,6 +402,16 @@
self.bonded_devices.get(&device.address).or_else(|| self.found_devices.get(&device.address))
}
+ fn get_remote_device_if_found_mut(
+ &mut self,
+ device: &BluetoothDevice,
+ ) -> Option<&mut BluetoothDeviceContext> {
+ match self.bonded_devices.get_mut(&device.address) {
+ None => self.found_devices.get_mut(&device.address),
+ some => some,
+ }
+ }
+
fn get_remote_device_property(
&self,
device: &BluetoothDevice,
@@ -407,6 +420,31 @@
self.get_remote_device_if_found(&device)
.and_then(|d| d.properties.get(property_type).and_then(|p| Some(p.clone())))
}
+
+ fn set_remote_device_property(
+ &mut self,
+ device: &BluetoothDevice,
+ property_type: BtPropertyType,
+ property: BluetoothProperty,
+ ) -> Result<(), ()> {
+ let mut remote_device = match self.get_remote_device_if_found_mut(&device) {
+ Some(d) => d,
+ None => {
+ return Err(());
+ }
+ };
+
+ let mut addr = RawAddress::from_string(device.address.clone());
+ if addr.is_none() {
+ return Err(());
+ }
+ let addr = addr.as_mut().unwrap();
+
+ // TODO: Determine why a callback isn't invoked to do this.
+ remote_device.properties.insert(property_type, property.clone());
+ self.intf.lock().unwrap().set_remote_device_property(addr, property);
+ Ok(())
+ }
}
#[btif_callbacks_dispatcher(Bluetooth, dispatch_base_callbacks, BaseCallbacks)]
@@ -1115,6 +1153,14 @@
}
}
+ fn set_remote_alias(&mut self, device: BluetoothDevice, new_alias: String) {
+ let _ = self.set_remote_device_property(
+ &device,
+ BtPropertyType::RemoteFriendlyName,
+ BluetoothProperty::RemoteFriendlyName(new_alias),
+ );
+ }
+
fn get_remote_class(&self, device: BluetoothDevice) -> u32 {
match self.get_remote_device_property(&device, &BtPropertyType::ClassOfDevice) {
Some(BluetoothProperty::ClassOfDevice(class)) => return class,
diff --git a/system/gd/rust/topshim/src/btif.rs b/system/gd/rust/topshim/src/btif.rs
index 625f815..452d7a6 100644
--- a/system/gd/rust/topshim/src/btif.rs
+++ b/system/gd/rust/topshim/src/btif.rs
@@ -382,7 +382,9 @@
let len = self.get_len();
match &*self {
BluetoothProperty::BdName(name) => {
- data.copy_from_slice(&name.as_bytes()[0..len]);
+ let copy_len = len - 1;
+ data[0..copy_len].copy_from_slice(&name.as_bytes()[0..copy_len]);
+ data[copy_len] = 0;
}
BluetoothProperty::BdAddr(addr) => {
data.copy_from_slice(&addr.val);
@@ -408,11 +410,12 @@
unsafe { &mut *(data.as_mut_ptr() as *mut bindings::bt_service_record_t) };
record.uuid = sr.uuid;
record.channel = sr.channel;
- let name_len = len - mem::size_of::<BtServiceRecord>();
- record.name.copy_from_slice(
+ let name_len = len - mem::size_of::<BtServiceRecord>() - 1;
+ record.name[0..name_len].copy_from_slice(
&(sr.name.as_bytes().iter().map(|x| *x as c_char).collect::<Vec<c_char>>())
[0..name_len],
);
+ record.name[name_len] = 0;
}
BluetoothProperty::AdapterScanMode(sm) => {
data.copy_from_slice(&BtScanMode::to_u32(sm).unwrap_or_default().to_ne_bytes());
@@ -428,7 +431,9 @@
data.copy_from_slice(&timeout.to_ne_bytes());
}
BluetoothProperty::RemoteFriendlyName(name) => {
- data.copy_from_slice(&name.as_bytes()[0..len]);
+ let copy_len = len - 1;
+ data[0..copy_len].copy_from_slice(&name.as_bytes()[0..copy_len]);
+ data[copy_len] = 0;
}
BluetoothProperty::RemoteRssi(rssi) => {
data[0] = *rssi as u8;
@@ -1110,4 +1115,46 @@
let expected: Vec<i32> = vec![1, 2, 3];
assert_eq!(expected, vec);
}
+
+ #[test]
+ fn test_property_with_string_conversions() {
+ {
+ let bdname = BluetoothProperty::BdName("FooBar".into());
+ let prop_pair: (Box<[u8]>, bindings::bt_property_t) = bdname.into();
+ let converted: BluetoothProperty = prop_pair.1.into();
+ assert!(match converted {
+ BluetoothProperty::BdName(name) => "FooBar".to_string() == name,
+ _ => false,
+ });
+ }
+
+ {
+ let orig_record = BtServiceRecord {
+ uuid: Uuid { uu: [0; 16] },
+ channel: 3,
+ name: "FooBar".to_string(),
+ };
+ let service_record = BluetoothProperty::ServiceRecord(orig_record.clone());
+ let prop_pair: (Box<[u8]>, bindings::bt_property_t) = service_record.into();
+ let converted: BluetoothProperty = prop_pair.1.into();
+ assert!(match converted {
+ BluetoothProperty::ServiceRecord(sr) => {
+ sr.uuid == orig_record.uuid
+ && sr.channel == orig_record.channel
+ && sr.name == orig_record.name
+ }
+ _ => false,
+ });
+ }
+
+ {
+ let rfname = BluetoothProperty::RemoteFriendlyName("FooBizz".into());
+ let prop_pair: (Box<[u8]>, bindings::bt_property_t) = rfname.into();
+ let converted: BluetoothProperty = prop_pair.1.into();
+ assert!(match converted {
+ BluetoothProperty::RemoteFriendlyName(name) => "FooBizz".to_string() == name,
+ _ => false,
+ });
+ }
+ }
}
diff --git a/system/stack/acl/btm_pm.cc b/system/stack/acl/btm_pm.cc
index 0ba4a23..1a6417b 100644
--- a/system/stack/acl/btm_pm.cc
+++ b/system/stack/acl/btm_pm.cc
@@ -238,7 +238,7 @@
(p_mode->min <= p_cb->interval)) ||
((p_mode->mode & BTM_PM_MD_FORCE) == 0 &&
(p_mode->max >= p_cb->interval))) {
- LOG_INFO(
+ LOG_DEBUG(
"Device is already in requested mode %d, interval: %d, max: %d, min: "
"%d",
p_mode->mode, p_cb->interval, p_mode->max, p_mode->min);
diff --git a/system/stack/btm/btm_ble.cc b/system/stack/btm/btm_ble.cc
index 5e1ab22..dfb136d 100644
--- a/system/stack/btm/btm_ble.cc
+++ b/system/stack/btm/btm_ble.cc
@@ -89,7 +89,11 @@
memset(p_dev_rec->sec_bd_name, 0, sizeof(tBTM_BD_NAME));
p_dev_rec->device_type |= dev_type;
- p_dev_rec->ble.SetAddressType(addr_type);
+ if (is_ble_addr_type_known(addr_type))
+ p_dev_rec->ble.SetAddressType(addr_type);
+ else
+ LOG_WARN(
+ "Please do not update device record from anonymous le advertisement");
/* sync up with the Inq Data base*/
tBTM_INQ_INFO* p_info = BTM_InqDbRead(bd_addr);
@@ -481,7 +485,12 @@
/* new inquiry result, overwrite device type in security device record */
if (p_inq_info) {
p_dev_rec->device_type = p_inq_info->results.device_type;
- p_dev_rec->ble.SetAddressType(p_inq_info->results.ble_addr_type);
+ if (is_ble_addr_type_known(p_inq_info->results.ble_addr_type))
+ p_dev_rec->ble.SetAddressType(p_inq_info->results.ble_addr_type);
+ else
+ LOG_WARN(
+ "Please do not update device record from anonymous le "
+ "advertisement");
}
if (p_dev_rec->bd_addr == remote_bda &&
@@ -1713,7 +1722,12 @@
p_dev_rec->timestamp = btm_cb.dev_rec_count++;
}
- p_dev_rec->ble.SetAddressType(addr_type);
+ if (is_ble_addr_type_known(addr_type))
+ p_dev_rec->ble.SetAddressType(addr_type);
+ else
+ LOG_WARN(
+ "Please do not update device record from anonymous le advertisement");
+
p_dev_rec->ble.pseudo_addr = bda;
p_dev_rec->ble_hci_handle = handle;
p_dev_rec->device_type |= BT_DEVICE_TYPE_BLE;
diff --git a/system/stack/btm/btm_dev.cc b/system/stack/btm/btm_dev.cc
index d887e93..eeb37be 100644
--- a/system/stack/btm/btm_dev.cc
+++ b/system/stack/btm/btm_dev.cc
@@ -237,7 +237,12 @@
memcpy(p_dev_rec->dev_class, p_inq_info->results.dev_class, DEV_CLASS_LEN);
p_dev_rec->device_type = p_inq_info->results.device_type;
- p_dev_rec->ble.SetAddressType(p_inq_info->results.ble_addr_type);
+ if (is_ble_addr_type_known(p_inq_info->results.ble_addr_type))
+ p_dev_rec->ble.SetAddressType(p_inq_info->results.ble_addr_type);
+ else
+ LOG_WARN(
+ "Please do not update device record from anonymous le advertisement");
+
} else if (bd_addr == btm_cb.connecting_bda)
memcpy(p_dev_rec->dev_class, btm_cb.connecting_dc, DEV_CLASS_LEN);
diff --git a/system/stack/btm/btm_int_types.h b/system/stack/btm/btm_int_types.h
index 2f8a7e5..360db83 100644
--- a/system/stack/btm/btm_int_types.h
+++ b/system/stack/btm/btm_int_types.h
@@ -39,6 +39,8 @@
#define BTM_MAX_SCN_ 31 // PORT_MAX_RFC_PORTS packages/modules/Bluetooth/system/stack/include/rfcdefs.h
constexpr size_t kMaxLogSize = 255;
+constexpr size_t kBtmLogHistoryBufferSize = 100;
+
class TimestampedStringCircularBuffer
: public bluetooth::common::TimestampedCircularBuffer<std::string> {
public:
@@ -347,7 +349,8 @@
sco_cb.Init(); /* SCO Database and Structures (If included) */
devcb.Init();
- history_ = std::make_shared<TimestampedStringCircularBuffer>(40);
+ history_ = std::make_shared<TimestampedStringCircularBuffer>(
+ kBtmLogHistoryBufferSize);
CHECK(history_ != nullptr);
history_->Push(std::string("Initialized btm history"));
}
diff --git a/system/test/rootcanal/bluetooth_hci.cc b/system/test/rootcanal/bluetooth_hci.cc
index 0df9bd8..dcc38dc 100644
--- a/system/test/rootcanal/bluetooth_hci.cc
+++ b/system/test/rootcanal/bluetooth_hci.cc
@@ -36,6 +36,7 @@
namespace sim {
using android::hardware::hidl_vec;
+using ::bluetooth::hci::Address;
using rootcanal::AsyncTaskId;
using rootcanal::DualModeController;
using rootcanal::HciDevice;
@@ -109,7 +110,12 @@
char mac_property[PROPERTY_VALUE_MAX] = "";
property_get("vendor.bt.rootcanal_mac_address", mac_property,
"3C:5A:B4:01:02:03");
- controller_->Initialize({"dmc", std::string(mac_property)});
+ auto addr = Address::FromString(std::string(mac_property));
+ if (addr) {
+ controller_->SetAddress(*addr);
+ } else {
+ LOG_ALWAYS_FATAL("Invalid address: %s", mac_property);
+ }
controller_->RegisterEventChannel(
[this, cb](std::shared_ptr<std::vector<uint8_t>> packet) {
diff --git a/system/tools/scripts/btsnooz.py b/system/tools/scripts/btsnooz.py
index 622824a..5613bd8 100755
--- a/system/tools/scripts/btsnooz.py
+++ b/system/tools/scripts/btsnooz.py
@@ -26,6 +26,7 @@
import struct
import sys
import zlib
+import subprocess
# Enumeration of the values the 'type' field can take in a btsnooz
# header. These values come from the Bluetooth stack's internal
@@ -56,15 +57,15 @@
Returns the HCI type of a packet given its btsnooz type.
"""
if type == TYPE_OUT_CMD:
- return '\x01'
+ return b'\x01'
if type == TYPE_IN_ACL or type == TYPE_OUT_ACL:
- return '\x02'
+ return b'\x02'
if type == TYPE_IN_SCO or type == TYPE_OUT_SCO:
- return '\x03'
+ return b'\x03'
if type == TYPE_IN_EVT:
- return '\x04'
+ return b'\x04'
if type == TYPE_IN_ISO or type == TYPE_OUT_ISO:
- return '\x05'
+ return b'\x05'
raise RuntimeError("type_to_hci: unknown type (0x{:02x})".format(type))
@@ -81,7 +82,7 @@
# Oddly, the file header (9 bytes) is not compressed, but the rest is.
decompressed = zlib.decompress(snooz[9:])
- sys.stdout.write('btsnoop\x00\x00\x00\x00\x01\x00\x00\x03\xea')
+ sys.stdout.buffer.write(b'btsnoop\x00\x00\x00\x00\x01\x00\x00\x03\xea')
if version == 1:
decode_snooz_v1(decompressed, last_timestamp_ms)
@@ -108,11 +109,11 @@
length, delta_time_ms, type = struct.unpack_from('=HIb', decompressed, offset)
first_timestamp_ms += delta_time_ms
offset += 7
- sys.stdout.write(struct.pack('>II', length, length))
- sys.stdout.write(struct.pack('>II', type_to_direction(type), 0))
- sys.stdout.write(struct.pack('>II', (first_timestamp_ms >> 32), (first_timestamp_ms & 0xFFFFFFFF)))
- sys.stdout.write(type_to_hci(type))
- sys.stdout.write(decompressed[offset:offset + length - 1])
+ sys.stdout.buffer.write(struct.pack('>II', length, length))
+ sys.stdout.buffer.write(struct.pack('>II', type_to_direction(type), 0))
+ sys.stdout.buffer.write(struct.pack('>II', (first_timestamp_ms >> 32), (first_timestamp_ms & 0xFFFFFFFF)))
+ sys.stdout.buffer.write(type_to_hci(type))
+ sys.stdout.buffer.write(decompressed[offset:offset + length - 1])
offset += length - 1
@@ -135,31 +136,58 @@
length, packet_length, delta_time_ms, snooz_type = struct.unpack_from('=HHIb', decompressed, offset)
first_timestamp_ms += delta_time_ms
offset += 9
- sys.stdout.write(struct.pack('>II', packet_length, length))
- sys.stdout.write(struct.pack('>II', type_to_direction(snooz_type), 0))
- sys.stdout.write(struct.pack('>II', (first_timestamp_ms >> 32), (first_timestamp_ms & 0xFFFFFFFF)))
- sys.stdout.write(type_to_hci(snooz_type))
- sys.stdout.write(decompressed[offset:offset + length - 1])
+ sys.stdout.buffer.write(struct.pack('>II', packet_length, length))
+ sys.stdout.buffer.write(struct.pack('>II', type_to_direction(snooz_type), 0))
+ sys.stdout.buffer.write(struct.pack('>II', (first_timestamp_ms >> 32), (first_timestamp_ms & 0xFFFFFFFF)))
+ sys.stdout.buffer.write(type_to_hci(snooz_type))
+ sys.stdout.buffer.write(decompressed[offset:offset + length - 1])
offset += length - 1
def main():
if len(sys.argv) > 2:
sys.stderr.write('Usage: %s [bugreport]\n' % sys.argv[0])
- exit(1)
+ sys.exit(1)
+
+ ## Assume the uudecoded data is being piped in
+ if not sys.stdin.isatty():
+ base64_string = ""
+ try:
+ for line in sys.stdin.readlines():
+ base64_string += line.strip()
+ decode_snooz(base64.standard_b64decode(base64_string))
+ sys.exit(0)
+ except Exception as e:
+ sys.stderr.write('Failed uudecoding...ensure input is a valid uuencoded stream.\n')
+ sys.stderr.write(e)
+ sys.exit(1)
iterator = fileinput.input()
+
found = False
base64_string = ""
- for line in iterator:
- if found:
- if line.find('--- END:BTSNOOP_LOG_SUMMARY') != -1:
- decode_snooz(base64.standard_b64decode(base64_string))
- sys.exit(0)
- base64_string += line.strip()
+ try:
+ for line in iterator:
+ if found:
+ if line.find('--- END:BTSNOOP_LOG_SUMMARY') != -1:
+ decode_snooz(base64.standard_b64decode(base64_string))
+ sys.exit(0)
+ base64_string += line.strip()
- if line.find('--- BEGIN:BTSNOOP_LOG_SUMMARY') != -1:
- found = True
+ if line.find('--- BEGIN:BTSNOOP_LOG_SUMMARY') != -1:
+ found = True
+
+ except UnicodeDecodeError:
+ ## Check if there is a BTSNOOP log uuencoded in the bugreport
+ p = subprocess.Popen(["egrep", "-a", "BTSNOOP_LOG_SUMMARY", sys.argv[1]], stdout=subprocess.PIPE)
+ p.wait()
+
+ if (p.returncode == 0):
+ sys.stderr.write('Failed to parse uuencoded btsnooz data from bugreport.\n')
+ sys.stderr.write(' Try:\n')
+ sys.stderr.write('LC_CTYPE=C sed -n "/BEGIN:BTSNOOP_LOG_SUMMARY/,/END:BTSNOOP_LOG_SUMMARY/p " ' +
+ sys.argv[1] + ' | egrep -av "BTSNOOP_LOG_SUMMARY" | ' + sys.argv[0] + ' > hci.log\n')
+ sys.exit(1)
if not found:
sys.stderr.write('No btsnooz section found in bugreport.\n')
diff --git a/tools/rootcanal/model/controller/dual_mode_controller.cc b/tools/rootcanal/model/controller/dual_mode_controller.cc
index 3770fcf..32a8fdb 100644
--- a/tools/rootcanal/model/controller/dual_mode_controller.cc
+++ b/tools/rootcanal/model/controller/dual_mode_controller.cc
@@ -38,17 +38,6 @@
constexpr uint16_t kLeMaximumDataTime = 0x148;
// Device methods.
-void DualModeController::Initialize(const std::vector<std::string>& args) {
- if (args.size() < 2) return;
-
- Address addr{};
- if (Address::FromString(args[1], addr)) {
- properties_.SetAddress(addr);
- } else {
- LOG_ALWAYS_FATAL("Invalid address: %s", args[1].c_str());
- }
-};
-
std::string DualModeController::GetTypeString() const {
return "Simulated Bluetooth Controller";
}
diff --git a/tools/rootcanal/model/controller/dual_mode_controller.h b/tools/rootcanal/model/controller/dual_mode_controller.h
index f9a4d28..8ef3cf3 100644
--- a/tools/rootcanal/model/controller/dual_mode_controller.h
+++ b/tools/rootcanal/model/controller/dual_mode_controller.h
@@ -62,8 +62,6 @@
~DualModeController() = default;
// Device methods.
- virtual void Initialize(const std::vector<std::string>& args) override;
-
virtual std::string GetTypeString() const override;
virtual void IncomingPacket(
diff --git a/tools/rootcanal/model/devices/beacon.cc b/tools/rootcanal/model/devices/beacon.cc
index e18a091..00a6983 100644
--- a/tools/rootcanal/model/devices/beacon.cc
+++ b/tools/rootcanal/model/devices/beacon.cc
@@ -40,6 +40,17 @@
'c'});
}
+Beacon::Beacon(const vector<std::string>& args) : Beacon() {
+ if (args.size() >= 2) {
+ Address addr{};
+ if (Address::FromString(args[1], addr)) properties_.SetLeAddress(addr);
+ }
+
+ if (args.size() >= 3) {
+ SetAdvertisementInterval(std::chrono::milliseconds(std::stoi(args[2])));
+ }
+}
+
std::string Beacon::GetTypeString() const { return "beacon"; }
std::string Beacon::ToString() const {
@@ -49,17 +60,6 @@
return dev;
}
-void Beacon::Initialize(const vector<std::string>& args) {
- if (args.size() < 2) return;
-
- Address addr{};
- if (Address::FromString(args[1], addr)) properties_.SetLeAddress(addr);
-
- if (args.size() < 3) return;
-
- SetAdvertisementInterval(std::chrono::milliseconds(std::stoi(args[2])));
-}
-
void Beacon::TimerTick() {
if (IsAdvertisementAvailable()) {
last_advertisement_ = std::chrono::steady_clock::now();
diff --git a/tools/rootcanal/model/devices/beacon.h b/tools/rootcanal/model/devices/beacon.h
index c0fc4ed..fc6af5c 100644
--- a/tools/rootcanal/model/devices/beacon.h
+++ b/tools/rootcanal/model/devices/beacon.h
@@ -27,9 +27,12 @@
class Beacon : public Device {
public:
Beacon();
+ Beacon(const std::vector<std::string>& args);
virtual ~Beacon() = default;
- static std::shared_ptr<Device> Create() { return std::make_shared<Beacon>(); }
+ static std::shared_ptr<Device> Create(const std::vector<std::string>& args) {
+ return std::make_shared<Beacon>(args);
+ }
// Return a string representation of the type of device.
virtual std::string GetTypeString() const override;
@@ -37,9 +40,6 @@
// Return a string representation of the device.
virtual std::string ToString() const override;
- // Set the address and advertising interval from string args.
- virtual void Initialize(const std::vector<std::string>& args) override;
-
virtual void IncomingPacket(
model::packets::LinkLayerPacketView packet) override;
diff --git a/tools/rootcanal/model/devices/beacon_swarm.cc b/tools/rootcanal/model/devices/beacon_swarm.cc
index ac2cea6..f47df40 100644
--- a/tools/rootcanal/model/devices/beacon_swarm.cc
+++ b/tools/rootcanal/model/devices/beacon_swarm.cc
@@ -24,7 +24,7 @@
bool BeaconSwarm::registered_ =
DeviceBoutique::Register("beacon_swarm", &BeaconSwarm::Create);
-BeaconSwarm::BeaconSwarm() {
+BeaconSwarm::BeaconSwarm(const vector<std::string>& args) : Beacon(args) {
advertising_interval_ms_ = std::chrono::milliseconds(1280);
properties_.SetLeAdvertisementType(0x03 /* NON_CONNECT */);
properties_.SetLeAdvertisement({
@@ -60,17 +60,6 @@
'c'});
}
-void BeaconSwarm::Initialize(const vector<std::string>& args) {
- if (args.size() < 2) return;
-
- Address addr{};
- if (Address::FromString(args[1], addr)) properties_.SetLeAddress(addr);
-
- if (args.size() < 3) return;
-
- SetAdvertisementInterval(std::chrono::milliseconds(std::stoi(args[2])));
-}
-
void BeaconSwarm::TimerTick() {
Address beacon_addr = properties_.GetLeAddress();
uint8_t* low_order_byte = (uint8_t*)(&beacon_addr);
diff --git a/tools/rootcanal/model/devices/beacon_swarm.h b/tools/rootcanal/model/devices/beacon_swarm.h
index 118e047..775aa7c 100644
--- a/tools/rootcanal/model/devices/beacon_swarm.h
+++ b/tools/rootcanal/model/devices/beacon_swarm.h
@@ -26,19 +26,16 @@
// Pretend to be a lot of beacons by changing the advertising address.
class BeaconSwarm : public Beacon {
public:
- BeaconSwarm();
+ BeaconSwarm(const std::vector<std::string>& args);
virtual ~BeaconSwarm() = default;
- static std::shared_ptr<Device> Create() {
- return std::make_shared<BeaconSwarm>();
+ static std::shared_ptr<Device> Create(const std::vector<std::string>& args) {
+ return std::make_shared<BeaconSwarm>(args);
}
// Return a string representation of the type of device.
virtual std::string GetTypeString() const override { return "beacon_swarm"; }
- // Set the address and advertising interval from string args.
- virtual void Initialize(const std::vector<std::string>& args) override;
-
virtual void TimerTick() override;
private:
diff --git a/tools/rootcanal/model/devices/broken_adv.cc b/tools/rootcanal/model/devices/broken_adv.cc
index 2de2017..5190f99 100644
--- a/tools/rootcanal/model/devices/broken_adv.cc
+++ b/tools/rootcanal/model/devices/broken_adv.cc
@@ -51,15 +51,15 @@
page_scan_delay_ms_ = std::chrono::milliseconds(600);
}
-void BrokenAdv::Initialize(const vector<std::string>& args) {
- if (args.size() < 2) return;
+BrokenAdv::BrokenAdv(const vector<std::string>& args) : BrokenAdv() {
+ if (args.size() >= 2) {
+ Address addr{};
+ if (Address::FromString(args[1], addr)) properties_.SetLeAddress(addr);
+ }
- Address addr{};
- if (Address::FromString(args[1], addr)) properties_.SetLeAddress(addr);
-
- if (args.size() < 3) return;
-
- SetAdvertisementInterval(std::chrono::milliseconds(std::stoi(args[2])));
+ if (args.size() >= 3) {
+ SetAdvertisementInterval(std::chrono::milliseconds(std::stoi(args[2])));
+ }
}
// Mostly return the correct length
diff --git a/tools/rootcanal/model/devices/broken_adv.h b/tools/rootcanal/model/devices/broken_adv.h
index 34c7369..e551ea2 100644
--- a/tools/rootcanal/model/devices/broken_adv.h
+++ b/tools/rootcanal/model/devices/broken_adv.h
@@ -26,15 +26,13 @@
class BrokenAdv : public Device {
public:
BrokenAdv();
+ BrokenAdv(const std::vector<std::string>& args);
~BrokenAdv() = default;
- static std::shared_ptr<Device> Create() {
- return std::make_shared<BrokenAdv>();
+ static std::shared_ptr<Device> Create(const std::vector<std::string>& args) {
+ return std::make_shared<BrokenAdv>(args);
}
- // Initialize the device based on the values of |args|.
- virtual void Initialize(const std::vector<std::string>& args) override;
-
// Return a string representation of the type of device.
virtual std::string GetTypeString() const override { return "broken_adv"; }
diff --git a/tools/rootcanal/model/devices/car_kit.cc b/tools/rootcanal/model/devices/car_kit.cc
index ba2c43e..ef92980 100644
--- a/tools/rootcanal/model/devices/car_kit.cc
+++ b/tools/rootcanal/model/devices/car_kit.cc
@@ -81,16 +81,16 @@
});
}
-void CarKit::Initialize(const vector<std::string>& args) {
- if (args.size() < 2) return;
+CarKit::CarKit(const vector<std::string>& args) : CarKit() {
+ if (args.size() >= 2) {
+ Address addr{};
+ if (Address::FromString(args[1], addr)) properties_.SetAddress(addr);
+ LOG_INFO("%s SetAddress %s", ToString().c_str(), addr.ToString().c_str());
+ }
- Address addr{};
- if (Address::FromString(args[1], addr)) properties_.SetAddress(addr);
- LOG_INFO("%s SetAddress %s", ToString().c_str(), addr.ToString().c_str());
-
- if (args.size() < 3) return;
-
- properties_.SetClockOffset(std::stoi(args[2]));
+ if (args.size() >= 3) {
+ properties_.SetClockOffset(std::stoi(args[2]));
+ }
}
void CarKit::TimerTick() { link_layer_controller_.TimerTick(); }
diff --git a/tools/rootcanal/model/devices/car_kit.h b/tools/rootcanal/model/devices/car_kit.h
index 8b4295d..f793e49 100644
--- a/tools/rootcanal/model/devices/car_kit.h
+++ b/tools/rootcanal/model/devices/car_kit.h
@@ -28,12 +28,12 @@
class CarKit : public Device {
public:
CarKit();
+ CarKit(const std::vector<std::string>& args);
~CarKit() = default;
- static std::shared_ptr<CarKit> Create() { return std::make_shared<CarKit>(); }
-
- // Initialize the device based on the values of |args|.
- virtual void Initialize(const std::vector<std::string>& args) override;
+ static std::shared_ptr<CarKit> Create(const std::vector<std::string>& args) {
+ return std::make_shared<CarKit>(args);
+ }
// Return a string representation of the type of device.
virtual std::string GetTypeString() const override { return "car_kit"; }
diff --git a/tools/rootcanal/model/devices/classic.cc b/tools/rootcanal/model/devices/classic.cc
index 732a826..60866c9 100644
--- a/tools/rootcanal/model/devices/classic.cc
+++ b/tools/rootcanal/model/devices/classic.cc
@@ -40,15 +40,15 @@
page_scan_delay_ms_ = std::chrono::milliseconds(600);
}
-void Classic::Initialize(const vector<std::string>& args) {
- if (args.size() < 2) return;
+Classic::Classic(const vector<std::string>& args) : Classic() {
+ if (args.size() >= 2) {
+ Address addr{};
+ if (Address::FromString(args[1], addr)) properties_.SetAddress(addr);
+ }
- Address addr{};
- if (Address::FromString(args[1], addr)) properties_.SetAddress(addr);
-
- if (args.size() < 3) return;
-
- properties_.SetClockOffset(std::stoi(args[2]));
+ if (args.size() >= 3) {
+ properties_.SetClockOffset(std::stoi(args[2]));
+ }
}
} // namespace rootcanal
diff --git a/tools/rootcanal/model/devices/classic.h b/tools/rootcanal/model/devices/classic.h
index 861c7cf..b3f6b65 100644
--- a/tools/rootcanal/model/devices/classic.h
+++ b/tools/rootcanal/model/devices/classic.h
@@ -26,15 +26,13 @@
class Classic : public Device {
public:
Classic();
+ Classic(const std::vector<std::string>& args);
~Classic() = default;
- static std::shared_ptr<Device> Create() {
- return std::make_shared<Classic>();
+ static std::shared_ptr<Device> Create(const std::vector<std::string>& args) {
+ return std::make_shared<Classic>(args);
}
- // Initialize the device based on the values of |args|.
- virtual void Initialize(const std::vector<std::string>& args) override;
-
// Return a string representation of the type of device.
virtual std::string GetTypeString() const override { return "classic"; }
diff --git a/tools/rootcanal/model/devices/device.h b/tools/rootcanal/model/devices/device.h
index f57f11f..25d1024 100644
--- a/tools/rootcanal/model/devices/device.h
+++ b/tools/rootcanal/model/devices/device.h
@@ -40,9 +40,6 @@
properties_(properties_filename) {}
virtual ~Device() = default;
- // Initialize the device based on the values of |args|.
- virtual void Initialize(const std::vector<std::string>& args) = 0;
-
// Return a string representation of the type of device.
virtual std::string GetTypeString() const = 0;
diff --git a/tools/rootcanal/model/devices/keyboard.cc b/tools/rootcanal/model/devices/keyboard.cc
index 5c818a0..9d219de 100644
--- a/tools/rootcanal/model/devices/keyboard.cc
+++ b/tools/rootcanal/model/devices/keyboard.cc
@@ -24,7 +24,7 @@
bool Keyboard::registered_ =
DeviceBoutique::Register("keyboard", &Keyboard::Create);
-Keyboard::Keyboard() {
+Keyboard::Keyboard(const vector<std::string>& args) : Beacon(args) {
properties_.SetLeAdvertisementType(0x00 /* CONNECTABLE */);
properties_.SetLeAdvertisement(
{0x11, // Length
@@ -63,17 +63,6 @@
std::string Keyboard::GetTypeString() const { return "keyboard"; }
-void Keyboard::Initialize(const vector<std::string>& args) {
- if (args.size() < 2) return;
-
- Address addr{};
- if (Address::FromString(args[1], addr)) properties_.SetLeAddress(addr);
-
- if (args.size() < 3) return;
-
- SetAdvertisementInterval(std::chrono::milliseconds(std::stoi(args[2])));
-}
-
void Keyboard::TimerTick() {
if (!connected_) {
Beacon::TimerTick();
diff --git a/tools/rootcanal/model/devices/keyboard.h b/tools/rootcanal/model/devices/keyboard.h
index a97e3f9..65620f3 100644
--- a/tools/rootcanal/model/devices/keyboard.h
+++ b/tools/rootcanal/model/devices/keyboard.h
@@ -26,19 +26,16 @@
class Keyboard : public Beacon {
public:
- Keyboard();
+ Keyboard(const std::vector<std::string>& args);
virtual ~Keyboard() = default;
- static std::shared_ptr<Device> Create() {
- return std::make_shared<Keyboard>();
+ static std::shared_ptr<Device> Create(const std::vector<std::string>& args) {
+ return std::make_shared<Keyboard>(args);
}
// Return a string representation of the type of device.
virtual std::string GetTypeString() const override;
- // Initialize the device based on the values of |args|.
- virtual void Initialize(const std::vector<std::string>& args) override;
-
virtual void IncomingPacket(
model::packets::LinkLayerPacketView packet) override;
diff --git a/tools/rootcanal/model/devices/link_layer_socket_device.h b/tools/rootcanal/model/devices/link_layer_socket_device.h
index 8ad89bd..d887760 100644
--- a/tools/rootcanal/model/devices/link_layer_socket_device.h
+++ b/tools/rootcanal/model/devices/link_layer_socket_device.h
@@ -48,8 +48,6 @@
return "link_layer_socket_device";
}
- virtual void Initialize(const std::vector<std::string>&) override {}
-
virtual void IncomingPacket(
model::packets::LinkLayerPacketView packet) override;
diff --git a/tools/rootcanal/model/devices/loopback.cc b/tools/rootcanal/model/devices/loopback.cc
index bd4ecdf..59c0959 100644
--- a/tools/rootcanal/model/devices/loopback.cc
+++ b/tools/rootcanal/model/devices/loopback.cc
@@ -44,6 +44,17 @@
'l', 'o', 'o', 'p'});
}
+Loopback::Loopback(const vector<std::string>& args) : Loopback() {
+ if (args.size() >= 2) {
+ Address addr{};
+ if (Address::FromString(args[1], addr)) properties_.SetLeAddress(addr);
+ }
+
+ if (args.size() >= 3) {
+ SetAdvertisementInterval(std::chrono::milliseconds(std::stoi(args[2])));
+ }
+}
+
std::string Loopback::GetTypeString() const { return "loopback"; }
std::string Loopback::ToString() const {
@@ -53,17 +64,6 @@
return dev;
}
-void Loopback::Initialize(const vector<std::string>& args) {
- if (args.size() < 2) return;
-
- Address addr{};
- if (Address::FromString(args[1], addr)) properties_.SetLeAddress(addr);
-
- if (args.size() < 3) return;
-
- SetAdvertisementInterval(std::chrono::milliseconds(std::stoi(args[2])));
-}
-
void Loopback::TimerTick() {}
void Loopback::IncomingPacket(model::packets::LinkLayerPacketView packet) {
diff --git a/tools/rootcanal/model/devices/loopback.h b/tools/rootcanal/model/devices/loopback.h
index 866a81d..d6af78d 100644
--- a/tools/rootcanal/model/devices/loopback.h
+++ b/tools/rootcanal/model/devices/loopback.h
@@ -27,10 +27,11 @@
class Loopback : public Device {
public:
Loopback();
+ Loopback(const std::vector<std::string>& args);
virtual ~Loopback() = default;
- static std::shared_ptr<Device> Create() {
- return std::make_shared<Loopback>();
+ static std::shared_ptr<Device> Create(const std::vector<std::string>& args) {
+ return std::make_shared<Loopback>(args);
}
// Return a string representation of the type of device.
@@ -39,9 +40,6 @@
// Return a string representation of the device.
virtual std::string ToString() const override;
- // Set the address and advertising interval from string args.
- virtual void Initialize(const std::vector<std::string>& args) override;
-
virtual void IncomingPacket(
model::packets::LinkLayerPacketView packet) override;
diff --git a/tools/rootcanal/model/devices/remote_loopback_device.cc b/tools/rootcanal/model/devices/remote_loopback_device.cc
index 3d5ceff..afe2754 100644
--- a/tools/rootcanal/model/devices/remote_loopback_device.cc
+++ b/tools/rootcanal/model/devices/remote_loopback_device.cc
@@ -35,13 +35,6 @@
return GetTypeString() + " (no address)";
}
-void RemoteLoopbackDevice::Initialize(const std::vector<std::string>& args) {
- if (args.size() < 2) return;
-
- Address addr{};
- if (Address::FromString(args[1], addr)) properties_.SetAddress(addr);
-}
-
void RemoteLoopbackDevice::IncomingPacket(
model::packets::LinkLayerPacketView packet) {
// TODO: Check sender?
diff --git a/tools/rootcanal/model/devices/remote_loopback_device.h b/tools/rootcanal/model/devices/remote_loopback_device.h
index 78c1ffb..496093b 100644
--- a/tools/rootcanal/model/devices/remote_loopback_device.h
+++ b/tools/rootcanal/model/devices/remote_loopback_device.h
@@ -28,7 +28,7 @@
RemoteLoopbackDevice();
virtual ~RemoteLoopbackDevice() = default;
- static std::shared_ptr<Device> Create() {
+ static std::shared_ptr<Device> Create(const std::vector<std::string>&) {
return std::make_shared<RemoteLoopbackDevice>();
}
@@ -38,8 +38,6 @@
virtual std::string ToString() const override;
- virtual void Initialize(const std::vector<std::string>& args) override;
-
virtual void IncomingPacket(
model::packets::LinkLayerPacketView packet) override;
diff --git a/tools/rootcanal/model/devices/scripted_beacon.cc b/tools/rootcanal/model/devices/scripted_beacon.cc
index b13c569..9c04be7 100644
--- a/tools/rootcanal/model/devices/scripted_beacon.cc
+++ b/tools/rootcanal/model/devices/scripted_beacon.cc
@@ -37,7 +37,8 @@
namespace rootcanal {
bool ScriptedBeacon::registered_ =
DeviceBoutique::Register("scripted_beacon", &ScriptedBeacon::Create);
-ScriptedBeacon::ScriptedBeacon() {
+
+ScriptedBeacon::ScriptedBeacon(const vector<std::string>& args) : Beacon(args) {
advertising_interval_ms_ = std::chrono::milliseconds(1280);
properties_.SetLeAdvertisementType(0x02 /* SCANNABLE */);
properties_.SetLeAdvertisement({
@@ -75,31 +76,20 @@
0x08, // TYPE_NAME_SHORT
'g', 'b', 'e', 'a'});
LOG_INFO("Scripted_beacon registered %s", registered_ ? "true" : "false");
-}
-bool has_time_elapsed(steady_clock::time_point time_point) {
- return steady_clock::now() > time_point;
-}
-
-void ScriptedBeacon::Initialize(const vector<std::string>& args) {
- if (args.size() < 2) {
- LOG_ERROR(
- "Initialization failed, need mac address, playback and playback events "
- "file arguments");
- return;
- }
-
- Address addr{};
- if (Address::FromString(args[1], addr)) properties_.SetLeAddress(addr);
-
- if (args.size() < 4) {
+ if (args.size() >= 4) {
+ config_file_ = args[2];
+ events_file_ = args[3];
+ set_state(PlaybackEvent::INITIALIZED);
+ } else {
LOG_ERROR(
"Initialization failed, need playback and playback events file "
"arguments");
}
- config_file_ = args[2];
- events_file_ = args[3];
- set_state(PlaybackEvent::INITIALIZED);
+}
+
+bool has_time_elapsed(steady_clock::time_point time_point) {
+ return steady_clock::now() > time_point;
}
void ScriptedBeacon::populate_event(PlaybackEvent* event,
diff --git a/tools/rootcanal/model/devices/scripted_beacon.h b/tools/rootcanal/model/devices/scripted_beacon.h
index 333480a..a5ea22a 100644
--- a/tools/rootcanal/model/devices/scripted_beacon.h
+++ b/tools/rootcanal/model/devices/scripted_beacon.h
@@ -30,11 +30,11 @@
// Pretend to be a lot of beacons by advertising from a file.
class ScriptedBeacon : public Beacon {
public:
- ScriptedBeacon();
+ ScriptedBeacon(const std::vector<std::string>& args);
virtual ~ScriptedBeacon() = default;
- static std::shared_ptr<Device> Create() {
- return std::make_shared<ScriptedBeacon>();
+ static std::shared_ptr<Device> Create(const std::vector<std::string>& args) {
+ return std::make_shared<ScriptedBeacon>(args);
}
// Return a string representation of the type of device.
@@ -46,9 +46,6 @@
return "scripted_beacon " + config_file_;
}
- // Set the address and advertising interval from string args.
- void Initialize(const std::vector<std::string>& args) override;
-
void TimerTick() override;
void IncomingPacket(model::packets::LinkLayerPacketView packet_view) override;
diff --git a/tools/rootcanal/model/devices/sniffer.cc b/tools/rootcanal/model/devices/sniffer.cc
index b57acd1..a38607b 100644
--- a/tools/rootcanal/model/devices/sniffer.cc
+++ b/tools/rootcanal/model/devices/sniffer.cc
@@ -26,16 +26,12 @@
bool Sniffer::registered_ =
DeviceBoutique::Register("sniffer", &Sniffer::Create);
-Sniffer::Sniffer() {}
-
-void Sniffer::Initialize(const vector<std::string>& args) {
- if (args.size() < 2) return;
-
- if (Address::FromString(args[1], device_to_sniff_)) {
- properties_.SetAddress(device_to_sniff_);
+Sniffer::Sniffer(const vector<std::string>& args) {
+ if (args.size() >= 2) {
+ if (Address::FromString(args[1], device_to_sniff_)) {
+ properties_.SetAddress(device_to_sniff_);
+ }
}
-
- if (args.size() < 3) return;
}
void Sniffer::TimerTick() {}
diff --git a/tools/rootcanal/model/devices/sniffer.h b/tools/rootcanal/model/devices/sniffer.h
index f72b4f7..2688655 100644
--- a/tools/rootcanal/model/devices/sniffer.h
+++ b/tools/rootcanal/model/devices/sniffer.h
@@ -29,16 +29,13 @@
class Sniffer : public Device {
public:
- Sniffer();
+ Sniffer(const std::vector<std::string>& args);
~Sniffer() = default;
- static std::shared_ptr<Sniffer> Create() {
- return std::make_shared<Sniffer>();
+ static std::shared_ptr<Sniffer> Create(const std::vector<std::string>& args) {
+ return std::make_shared<Sniffer>(args);
}
- // Initialize the device based on the values of |args|.
- virtual void Initialize(const std::vector<std::string>& args) override;
-
// Return a string representation of the type of device.
virtual std::string GetTypeString() const override { return "sniffer"; }
diff --git a/tools/rootcanal/model/setup/device_boutique.cc b/tools/rootcanal/model/setup/device_boutique.cc
index d95fe9d..3acb5b5 100644
--- a/tools/rootcanal/model/setup/device_boutique.cc
+++ b/tools/rootcanal/model/setup/device_boutique.cc
@@ -22,10 +22,11 @@
namespace rootcanal {
-std::unordered_map<std::string, std::function<std::shared_ptr<Device>()>>&
+std::unordered_map<std::string, std::function<std::shared_ptr<Device>(
+ const vector<std::string>&)>>&
DeviceBoutique::GetMap() {
- static std::unordered_map<std::string,
- std::function<std::shared_ptr<Device>()>>
+ static std::unordered_map<std::string, std::function<std::shared_ptr<Device>(
+ const vector<std::string>&)>>
impl;
return impl;
}
@@ -33,9 +34,10 @@
// Register a constructor for a device type.
bool DeviceBoutique::Register(
const std::string& device_type,
- const std::function<std::shared_ptr<Device>()> device_constructor) {
+ const std::function<std::shared_ptr<Device>(const vector<std::string>&)>
+ method) {
LOG_INFO("Registering %s", device_type.c_str());
- GetMap()[device_type] = device_constructor;
+ GetMap()[device_type] = method;
return true;
}
@@ -43,15 +45,14 @@
const vector<std::string>& args) {
ASSERT(!args.empty());
- if (GetMap().find(args[0]) == GetMap().end()) {
+ auto device = GetMap().find(args[0]);
+
+ if (device == GetMap().end()) {
LOG_WARN("No constructor registered for %s", args[0].c_str());
return std::shared_ptr<Device>(nullptr);
}
- std::shared_ptr<Device> new_device = GetMap()[args[0]]();
- if (new_device != nullptr) new_device->Initialize(args);
-
- return new_device;
+ return device->second(args);
}
} // namespace rootcanal
diff --git a/tools/rootcanal/model/setup/device_boutique.h b/tools/rootcanal/model/setup/device_boutique.h
index a07b48c..562003c 100644
--- a/tools/rootcanal/model/setup/device_boutique.h
+++ b/tools/rootcanal/model/setup/device_boutique.h
@@ -34,14 +34,16 @@
// Register a constructor for a device type.
static bool Register(const std::string& device_type,
- const std::function<std::shared_ptr<Device>()> method);
+ const std::function<std::shared_ptr<Device>(
+ const std::vector<std::string>&)>
+ method);
- // Call the constructor that matches arg[0], then call dev->Initialize(args).
+ // Call the function that matches arg[0] with args
static std::shared_ptr<Device> Create(const std::vector<std::string>& args);
private:
- static std::unordered_map<std::string,
- std::function<std::shared_ptr<Device>()>>&
+ static std::unordered_map<std::string, std::function<std::shared_ptr<Device>(
+ const std::vector<std::string>&)>>&
GetMap();
};
diff --git a/tools/rootcanal/model/setup/test_model.cc b/tools/rootcanal/model/setup/test_model.cc
index c2ad281..2746180 100644
--- a/tools/rootcanal/model/setup/test_model.cc
+++ b/tools/rootcanal/model/setup/test_model.cc
@@ -173,13 +173,12 @@
void TestModel::AddHciConnection(std::shared_ptr<HciDevice> dev) {
size_t index = Add(std::static_pointer_cast<Device>(dev));
- std::string addr = "da:4c:10:de:17:"; // Da HCI dev
- std::stringstream stream;
- stream << std::setfill('0') << std::setw(2) << std::hex << (index % 256);
- addr += stream.str();
- dev->Initialize({"IgnoredTypeName", addr});
- LOG_INFO("initialized %s", addr.c_str());
+ uint8_t raw[] = {0xda, 0x4c, 0x10, 0xde, 0x17, uint8_t(index)}; // Da HCI dev
+ auto addr = Address{{raw[5], raw[4], raw[3], raw[2], raw[1], raw[0]}};
+ dev->SetAddress(addr);
+
+ LOG_INFO("initialized %s", addr.ToString().c_str());
for (size_t i = 0; i < phys_.size(); i++) {
AddDeviceToPhy(index, i);
}