Merge "Revert "Remove second action on tBTA_DM_PM_SPEC"" into tm-qpr-dev
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 65f2fef..c1b9e29 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -1,75 +1,220 @@
{
- "postsubmit" : [
+ "presubmit": [
+ // android_test targets
{
- "name" : "bluetooth_test_common"
+ "name": "CtsBluetoothTestCases"
},
{
- "name" : "bluetoothtbd_test"
+ "name": "BluetoothInstrumentationTests"
},
{
- "name" : "net_test_audio_a2dp_hw"
+ "name": "GoogleBluetoothInstrumentationTests"
},
{
- "name" : "net_test_avrcp"
+ "name": "FrameworkBluetoothTests"
},
{
- "name" : "net_test_btcore"
+ "name": "ServiceBluetoothTests"
+ },
+ // device only tests
+ // Broken
+ //{
+ // "name": "bluetooth-test-audio-hal-interface"
+ //},
+ {
+ "name": "net_test_audio_a2dp_hw"
},
{
- "name" : "net_test_btif"
+ "name": "net_test_audio_hearing_aid_hw"
+ },
+ // TODO (b/267212763)
+ // {
+ // "name": "net_test_bluetooth"
+ // },
+ // {
+ // "name": "net_test_bta"
+ // },
+ // {
+ // "name": "net_test_btif"
+ // },
+ {
+ "name": "net_test_btif_hf_client_service"
},
{
- "name" : "net_test_btif_profile_queue"
+ "name": "net_test_btif_profile_queue"
},
{
- "name" : "net_test_btpackets"
+ "name": "net_test_device"
},
{
- "name" : "net_test_device"
+ "name": "net_test_gatt_conn_multiplexing"
+ },
+ // TODO (b/267212763)
+ // {
+ // "name": "net_test_hci"
+ // },
+ {
+ "name": "net_test_hf_client_add_record"
+ },
+ // TODO (b/267212763)
+ // {
+ // "name": "net_test_stack"
+ // },
+ {
+ "name": "net_test_stack_ad_parser"
},
{
- "name" : "net_test_eatt"
+ "name": "net_test_stack_multi_adv"
+ },
+ // go/a-unit-tests tests (unit_test: true)
+ // Thoses test run on the host in the CI automatically.
+ // Run the one that are available on the device on the
+ // device as well
+ // TODO (b/267212763)
+ // {
+ // "name": "bluetooth_csis_test"
+ // },
+ {
+ "name": "bluetooth_flatbuffer_tests"
+ },
+ // TODO (b/267212763)
+ // {
+ // "name": "bluetooth_groups_test"
+ // },
+ // {
+ // "name": "bluetooth_has_test"
+ // },
+ {
+ "name": "bluetooth_hh_test"
+ },
+ // TODO (b/267212763)
+ // {
+ // "name": "bluetooth_le_audio_client_test"
+ // },
+ {
+ "name": "bluetooth_le_audio_test"
},
{
- "name" : "net_test_hci"
+ "name": "bluetooth_packet_parser_test"
+ },
+ // TODO (b/267212763)
+ // {
+ // "name": "bluetooth_test_broadcaster"
+ // },
+ // {
+ // "name": "bluetooth_test_broadcaster_state_machine"
+ // },
+ {
+ "name": "bluetooth_test_common"
},
{
- "name" : "net_test_performance"
+ "name": "bluetooth_test_gd_unit"
},
{
- "name" : "net_test_stack"
+ "name": "bluetooth_test_sdp"
+ },
+ // TODO (b/267212763)
+ // {
+ // "name": "bluetooth_vc_test"
+ // },
+ // {
+ // "name": "bluetoothtbd_test"
+ // },
+ // {
+ // "name": "bt_host_test_bta"
+ // },
+ {
+ "name": "libaptx_enc_tests"
},
{
- "name" : "net_test_stack_ad_parser"
+ "name": "libaptxhd_enc_tests"
},
{
- "name" : "net_test_stack_multi_adv"
+ "name": "net_test_avrcp"
},
{
- "name" : "net_test_stack_rfcomm"
+ "name": "net_test_btcore"
},
{
- "name" : "net_test_stack_smp"
+ "name": "net_test_btif_config_cache"
+ },
+ // TODO (b/267212763)
+ // {
+ // "name": "net_test_btif_hh"
+ // },
+ {
+ "name": "net_test_btif_rc"
+ },
+ // TODO (b/267212763)
+ // {
+ // "name": "net_test_btif_stack"
+ // },
+ {
+ "name": "net_test_btm_iso"
},
{
- "name" : "net_test_types"
- }
- ],
- "presubmit" : [
- {
- "name" : "net_test_hf_client_add_record"
+ "name": "net_test_btpackets"
},
{
- "name" : "net_test_btif_hf_client_service"
+ "name": "net_test_eatt"
},
{
- "name" : "libaptx_enc_tests"
+ "name": "net_test_hci_fragmenter_native"
},
{
- "name" : "libaptxhd_enc_tests"
+ "name": "net_test_main_shim"
},
{
- "name" : "net_test_stack_btm"
+ "name": "net_test_osi"
+ },
+ {
+ "name": "net_test_performance"
+ },
+ {
+ "name": "net_test_stack_a2dp_native"
+ },
+ {
+ "name": "net_test_stack_acl"
+ },
+ {
+ "name": "net_test_stack_avdtp"
+ },
+ // TODO (b/267212763)
+ // {
+ // "name": "net_test_stack_btm"
+ // },
+ {
+ "name": "net_test_stack_btu"
+ },
+ {
+ "name": "net_test_stack_gatt"
+ },
+ {
+ "name": "net_test_stack_gatt_native"
+ },
+ {
+ "name": "net_test_stack_gatt_sr_hash_native"
+ },
+ {
+ "name": "net_test_stack_hci"
+ },
+ {
+ "name": "net_test_stack_hid"
+ },
+ {
+ "name": "net_test_stack_l2cap"
+ },
+ {
+ "name": "net_test_stack_rfcomm"
+ },
+ {
+ "name": "net_test_stack_sdp"
+ },
+ {
+ "name": "net_test_stack_smp"
+ },
+ {
+ "name": "net_test_types"
}
]
}
diff --git a/android/app/jni/com_android_bluetooth_hfpclient.cpp b/android/app/jni/com_android_bluetooth_hfpclient.cpp
index a36e19c..da4dff9 100644
--- a/android/app/jni/com_android_bluetooth_hfpclient.cpp
+++ b/android/app/jni/com_android_bluetooth_hfpclient.cpp
@@ -879,6 +879,37 @@
return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}
+static jboolean sendAndroidAtNative(JNIEnv* env, jobject object,
+ jbyteArray address, jstring arg_str) {
+ std::shared_lock<std::shared_mutex> lock(interface_mutex);
+ if (!sBluetoothHfpClientInterface) return JNI_FALSE;
+
+ jbyte* addr = env->GetByteArrayElements(address, NULL);
+ if (!addr) {
+ jniThrowIOException(env, EINVAL);
+ return JNI_FALSE;
+ }
+
+ const char* arg = NULL;
+ if (arg_str != NULL) {
+ arg = env->GetStringUTFChars(arg_str, NULL);
+ }
+
+ bt_status_t status = sBluetoothHfpClientInterface->send_android_at(
+ (const RawAddress*)addr, arg);
+
+ if (status != BT_STATUS_SUCCESS) {
+ ALOGE("FAILED to control volume, status: %d", status);
+ }
+
+ if (arg != NULL) {
+ env->ReleaseStringUTFChars(arg_str, arg);
+ }
+
+ env->ReleaseByteArrayElements(address, addr, 0);
+ return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE;
+}
+
static JNINativeMethod sMethods[] = {
{"classInitNative", "()V", (void*)classInitNative},
{"initializeNative", "()V", (void*)initializeNative},
@@ -903,6 +934,8 @@
{"requestLastVoiceTagNumberNative", "([B)Z",
(void*)requestLastVoiceTagNumberNative},
{"sendATCmdNative", "([BIIILjava/lang/String;)Z", (void*)sendATCmdNative},
+ {"sendAndroidAtNative", "([BLjava/lang/String;)Z",
+ (void*)sendAndroidAtNative},
};
int register_com_android_bluetooth_hfpclient(JNIEnv* env) {
diff --git a/android/app/res/layout/bluetooth_map_settings.xml b/android/app/res/layout/bluetooth_map_settings.xml
index f256a02..0c7e3b3 100644
--- a/android/app/res/layout/bluetooth_map_settings.xml
+++ b/android/app/res/layout/bluetooth_map_settings.xml
@@ -17,7 +17,7 @@
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
- android:id="@+id/bluetooth_map_settings_liniar_layout"
+ android:id="@+id/bluetooth_map_settings_linear_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
diff --git a/android/app/res/values-ky/strings.xml b/android/app/res/values-ky/strings.xml
index 954556f..014f3c5 100644
--- a/android/app/res/values-ky/strings.xml
+++ b/android/app/res/values-ky/strings.xml
@@ -121,7 +121,7 @@
<string name="bluetooth_map_settings_intro" msgid="4748160773998753325">"Bluetooth аркылуу бөлүшө турган аккаунттарды тандаңыз. Туташкан сайын аккаунттарга кирүүгө уруксат берип турушуңуз керек."</string>
<string name="bluetooth_map_settings_count" msgid="183013143617807702">"Калган көзөнөктөр:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="3501432663809664982">"Колдонмонун сүрөтчөсү"</string>
- <string name="bluetooth_map_settings_title" msgid="4226030082708590023">"Bluetooth билдирүү бөлүшүү жөндөөлөрү"</string>
+ <string name="bluetooth_map_settings_title" msgid="4226030082708590023">"Bluetooth билдирүү бөлүшүү параметрлери"</string>
<string name="bluetooth_map_settings_no_account_slots_left" msgid="755024228476065757">"Аккаунт тандалбай жатат: 0 орун калды"</string>
<string name="bluetooth_connected" msgid="5687474377090799447">"Bluetooth аудио туташты"</string>
<string name="bluetooth_disconnected" msgid="6841396291728343534">"Bluetooth аудио ажыратылды"</string>
diff --git a/android/app/res/values/config.xml b/android/app/res/values/config.xml
index 913c77f..1e062b5 100644
--- a/android/app/res/values/config.xml
+++ b/android/app/res/values/config.xml
@@ -33,6 +33,34 @@
<integer name="gatt_low_power_min_interval">80</integer>
<integer name="gatt_low_power_max_interval">100</integer>
+ <!-- min/max connection intervals/latencies for companion devices -->
+ <!-- Primary companion -->
+ <integer name="gatt_high_priority_min_interval_primary">6</integer>
+ <integer name="gatt_high_priority_max_interval_primary">8</integer>
+ <integer name="gatt_high_priority_latency_primary">45</integer>
+
+ <integer name="gatt_balanced_priority_min_interval_primary">6</integer>
+ <integer name="gatt_balanced_priority_max_interval_primary">10</integer>
+ <integer name="gatt_balanced_priority_latency_primary">120</integer>
+
+ <integer name="gatt_low_power_min_interval_primary">8</integer>
+ <integer name="gatt_low_power_max_interval_primary">10</integer>
+ <integer name="gatt_low_power_latency_primary">150</integer>
+
+ <!-- Secondary companion -->
+ <integer name="gatt_high_priority_min_interval_secondary">6</integer>
+ <integer name="gatt_high_priority_max_interval_secondary">6</integer>
+ <integer name="gatt_high_priority_latency_secondary">0</integer>
+
+ <integer name="gatt_balanced_priority_min_interval_secondary">12</integer>
+ <integer name="gatt_balanced_priority_max_interval_secondary">12</integer>
+ <integer name="gatt_balanced_priority_latency_secondary">30</integer>
+
+ <integer name="gatt_low_power_min_interval_secondary">80</integer>
+ <integer name="gatt_low_power_max_interval_secondary">100</integer>
+ <integer name="gatt_low_power_latency_secondary">15</integer>
+ <!-- ============================================================ -->
+
<!-- Specifies latency parameters for high priority, balanced and low power
GATT configurations. These values represents the number of packets a
peripheral device is allowed to skip. -->
diff --git a/android/app/res/values/styles.xml b/android/app/res/values/styles.xml
index 91f402d..ea94695 100644
--- a/android/app/res/values/styles.xml
+++ b/android/app/res/values/styles.xml
@@ -31,7 +31,7 @@
<item name="android:paddingTop">10dip</item>
<item name="android:textAlignment">viewStart</item>
<item name="android:textAppearance">@android:style/TextAppearance.Material.Body1</item>
- <item name="android:textColor">@*android:color/secondary_text_default_material_light</item>
+ <item name="android:textColor">?android:attr/textColorSecondary</item>
</style>
<style name="file_transfer_item_content">
@@ -42,7 +42,7 @@
<item name="android:paddingBottom">10dip</item>
<item name="android:textAlignment">viewStart</item>
<item name="android:textAppearance">@android:style/TextAppearance.Material.Subhead</item>
- <item name="android:textColor">@*android:color/primary_text_default_material_light</item>
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
</style>
<style name="dialog" parent="android:style/Theme.Material.Light.Dialog.Alert" />
diff --git a/android/app/src/com/android/bluetooth/BluetoothMethodProxy.java b/android/app/src/com/android/bluetooth/BluetoothMethodProxy.java
index d1ea7d4..00cf686 100644
--- a/android/app/src/com/android/bluetooth/BluetoothMethodProxy.java
+++ b/android/app/src/com/android/bluetooth/BluetoothMethodProxy.java
@@ -179,6 +179,13 @@
}
/**
+ * Proxies {@link Handler#sendEmptyMessage(int)}}.
+ */
+ public boolean handlerSendEmptyMessage(Handler handler, final int what) {
+ return handler.sendEmptyMessage(what);
+ }
+
+ /**
* Proxies {@link HeaderSet#getHeader}.
*/
public Object getHeader(HeaderSet headerSet, int headerId) throws IOException {
diff --git a/android/app/src/com/android/bluetooth/Utils.java b/android/app/src/com/android/bluetooth/Utils.java
index dc2c79a..b22b3aa 100644
--- a/android/app/src/com/android/bluetooth/Utils.java
+++ b/android/app/src/com/android/bluetooth/Utils.java
@@ -1008,7 +1008,8 @@
}
values.put(Telephony.Sms.ERROR_CODE, 0);
- return 1 == context.getContentResolver().update(uri, values, null, null);
+ return 1 == BluetoothMethodProxy.getInstance().contentResolverUpdate(
+ context.getContentResolver(), uri, values, null, null);
}
/**
diff --git a/android/app/src/com/android/bluetooth/a2dp/A2dpService.java b/android/app/src/com/android/bluetooth/a2dp/A2dpService.java
index 9bc4fad..7ca6166 100644
--- a/android/app/src/com/android/bluetooth/a2dp/A2dpService.java
+++ b/android/app/src/com/android/bluetooth/a2dp/A2dpService.java
@@ -106,7 +106,6 @@
boolean mA2dpOffloadEnabled = false;
private BroadcastReceiver mBondStateChangedReceiver;
- private BroadcastReceiver mConnectionStateChangedReceiver;
public static boolean isEnabled() {
return BluetoothProperties.isProfileA2dpSourceEnabled().orElse(false);
@@ -169,10 +168,6 @@
filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
mBondStateChangedReceiver = new BondStateChangedReceiver();
registerReceiver(mBondStateChangedReceiver, filter);
- filter = new IntentFilter();
- filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
- mConnectionStateChangedReceiver = new ConnectionStateChangedReceiver();
- registerReceiver(mConnectionStateChangedReceiver, filter);
// Step 8: Mark service as started
setA2dpService(this);
@@ -208,8 +203,6 @@
setA2dpService(null);
// Step 7: Unregister broadcast receivers
- unregisterReceiver(mConnectionStateChangedReceiver);
- mConnectionStateChangedReceiver = null;
unregisterReceiver(mBondStateChangedReceiver);
mBondStateChangedReceiver = null;
@@ -1242,7 +1235,7 @@
}
}
- private void connectionStateChanged(BluetoothDevice device, int fromState, int toState) {
+ void connectionStateChanged(BluetoothDevice device, int fromState, int toState) {
if ((device == null) || (fromState == toState)) {
return;
}
@@ -1269,27 +1262,6 @@
}
/**
- * Receiver for processing device connection state changes.
- *
- * <ul>
- * <li> Update codec support per device when device is (re)connected
- * <li> Delete the state machine instance if the device is disconnected and unbond
- * </ul>
- */
- private class ConnectionStateChangedReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (!BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())) {
- return;
- }
- BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
- int toState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
- int fromState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1);
- connectionStateChanged(device, fromState, toState);
- }
- }
-
- /**
* Retrieves the most recently connected device in the A2DP connected devices list.
*/
public BluetoothDevice getFallbackDevice() {
diff --git a/android/app/src/com/android/bluetooth/a2dp/A2dpStateMachine.java b/android/app/src/com/android/bluetooth/a2dp/A2dpStateMachine.java
index 9e7323f..11d33ad 100644
--- a/android/app/src/com/android/bluetooth/a2dp/A2dpStateMachine.java
+++ b/android/app/src/com/android/bluetooth/a2dp/A2dpStateMachine.java
@@ -701,6 +701,7 @@
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
| Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+ mA2dpService.connectionStateChanged(mDevice, prevState, newState);
mA2dpService.sendBroadcast(intent, BLUETOOTH_CONNECT,
Utils.getTempAllowlistBroadcastOptions());
}
diff --git a/android/app/src/com/android/bluetooth/audio_util/MediaPlayerList.java b/android/app/src/com/android/bluetooth/audio_util/MediaPlayerList.java
index 1eebe99..97df3ce 100644
--- a/android/app/src/com/android/bluetooth/audio_util/MediaPlayerList.java
+++ b/android/app/src/com/android/bluetooth/audio_util/MediaPlayerList.java
@@ -31,6 +31,7 @@
import android.media.session.PlaybackState;
import android.os.Handler;
import android.os.Looper;
+import android.os.SystemProperties;
import android.text.TextUtils;
import android.util.Log;
import android.view.KeyEvent;
@@ -146,10 +147,45 @@
mContext.getMainExecutor(), mMediaKeyEventSessionChangedListener);
}
+ private void constructCurrentPlayers() {
+ // Construct the list of current players
+ d("Initializing list of current media players");
+ List<android.media.session.MediaController> controllers =
+ mMediaSessionManager.getActiveSessions(null);
+
+ for (android.media.session.MediaController controller : controllers) {
+ addMediaPlayer(controller);
+ }
+
+ // If there were any active players and we don't already have one due to the Media
+ // Framework Callbacks then set the highest priority one to active
+ if (mActivePlayerId == 0 && mMediaPlayers.size() > 0) {
+ String packageName = mMediaSessionManager.getMediaKeyEventSessionPackageName();
+ if (!TextUtils.isEmpty(packageName) && haveMediaPlayer(packageName)) {
+ Log.i(TAG, "Set active player to MediaKeyEvent session = " + packageName);
+ setActivePlayer(mMediaPlayerIds.get(packageName));
+ } else {
+ Log.i(TAG, "Set active player to first default");
+ setActivePlayer(1);
+ }
+ }
+ }
+
public void init(MediaUpdateCallback callback) {
Log.v(TAG, "Initializing MediaPlayerList");
mCallback = callback;
+ if (!SystemProperties.getBoolean("bluetooth.avrcp.browsable_media_player.enabled", true)) {
+ // Allow to disable BrowsablePlayerConnector with systemproperties.
+ // This is useful when for watches because:
+ // 1. It is not a regular use case
+ // 2. Registering to all players is a very loading task
+
+ Log.i(TAG, "init: without Browsable Player");
+ constructCurrentPlayers();
+ return;
+ }
+
// Build the list of browsable players and afterwards, build the list of media players
Intent intent = new Intent(android.service.media.MediaBrowserService.SERVICE_INTERFACE);
List<ResolveInfo> playerList =
@@ -185,27 +221,7 @@
});
}
- // Construct the list of current players
- d("Initializing list of current media players");
- List<android.media.session.MediaController> controllers =
- mMediaSessionManager.getActiveSessions(null);
-
- for (android.media.session.MediaController controller : controllers) {
- addMediaPlayer(controller);
- }
-
- // If there were any active players and we don't already have one due to the Media
- // Framework Callbacks then set the highest priority one to active
- if (mActivePlayerId == 0 && mMediaPlayers.size() > 0) {
- String packageName = mMediaSessionManager.getMediaKeyEventSessionPackageName();
- if (!TextUtils.isEmpty(packageName) && haveMediaPlayer(packageName)) {
- Log.i(TAG, "Set active player to MediaKeyEvent session = " + packageName);
- setActivePlayer(mMediaPlayerIds.get(packageName));
- } else {
- Log.i(TAG, "Set active player to first default");
- setActivePlayer(1);
- }
- }
+ constructCurrentPlayers();
});
}
diff --git a/android/app/src/com/android/bluetooth/avrcp/AvrcpVolumeManager.java b/android/app/src/com/android/bluetooth/avrcp/AvrcpVolumeManager.java
index d0cfa58..5cf76bd 100644
--- a/android/app/src/com/android/bluetooth/avrcp/AvrcpVolumeManager.java
+++ b/android/app/src/com/android/bluetooth/avrcp/AvrcpVolumeManager.java
@@ -29,6 +29,7 @@
import android.util.Log;
import com.android.bluetooth.audio_util.BTAudioEventLogger;
+import com.android.internal.annotations.VisibleForTesting;
import java.util.HashMap;
import java.util.Map;
@@ -42,7 +43,9 @@
private static final String VOLUME_MAP = "bluetooth_volume_map";
private static final String VOLUME_REJECTLIST = "absolute_volume_rejectlist";
private static final String VOLUME_CHANGE_LOG_TITLE = "Volume Events";
- private static final int AVRCP_MAX_VOL = 127;
+
+ @VisibleForTesting
+ static final int AVRCP_MAX_VOL = 127;
private static final int STREAM_MUSIC = AudioManager.STREAM_MUSIC;
private static final int VOLUME_CHANGE_LOGGER_SIZE = 30;
private static int sDeviceMaxVolume = 0;
diff --git a/android/app/src/com/android/bluetooth/btservice/ActiveDeviceManager.java b/android/app/src/com/android/bluetooth/btservice/ActiveDeviceManager.java
index f043394..03160d4 100644
--- a/android/app/src/com/android/bluetooth/btservice/ActiveDeviceManager.java
+++ b/android/app/src/com/android/bluetooth/btservice/ActiveDeviceManager.java
@@ -26,6 +26,7 @@
import android.bluetooth.BluetoothHearingAid;
import android.bluetooth.BluetoothLeAudio;
import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothSinkAudioPolicy;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -689,10 +690,14 @@
if (headsetService == null) {
return;
}
- if (!headsetService.setActiveDevice(device)) {
- return;
+ BluetoothSinkAudioPolicy audioPolicy = headsetService.getHfpCallAudioPolicy(device);
+ if (audioPolicy == null || audioPolicy.getActiveDevicePolicyAfterConnection()
+ != BluetoothSinkAudioPolicy.POLICY_NOT_ALLOWED) {
+ if (!headsetService.setActiveDevice(device)) {
+ return;
+ }
+ mHfpActiveDevice = device;
}
- mHfpActiveDevice = device;
}
private void setHearingAidActiveDevice(BluetoothDevice device) {
diff --git a/android/app/src/com/android/bluetooth/btservice/AdapterProperties.java b/android/app/src/com/android/bluetooth/btservice/AdapterProperties.java
index ad7739c..77b2b4e 100644
--- a/android/app/src/com/android/bluetooth/btservice/AdapterProperties.java
+++ b/android/app/src/com/android/bluetooth/btservice/AdapterProperties.java
@@ -55,6 +55,8 @@
import com.android.bluetooth.Utils;
import com.android.bluetooth.btservice.RemoteDevices.DeviceProperties;
+import com.google.common.collect.EvictingQueue;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -91,6 +93,9 @@
private CopyOnWriteArrayList<BluetoothDevice> mBondedDevices =
new CopyOnWriteArrayList<BluetoothDevice>();
+ private static final int SCAN_MODE_CHANGES_MAX_SIZE = 10;
+ private EvictingQueue<String> mScanModeChanges;
+
private int mProfilesConnecting, mProfilesConnected, mProfilesDisconnecting;
private final HashMap<Integer, Pair<Integer, Integer>> mProfileConnectionState =
new HashMap<>();
@@ -200,6 +205,7 @@
AdapterProperties(AdapterService service) {
mService = service;
mAdapter = BluetoothAdapter.getDefaultAdapter();
+ mScanModeChanges = EvictingQueue.create(SCAN_MODE_CHANGES_MAX_SIZE);
invalidateBluetoothCaches();
}
@@ -254,6 +260,7 @@
}
mService = null;
mBondedDevices.clear();
+ mScanModeChanges.clear();
invalidateBluetoothCaches();
}
@@ -389,15 +396,28 @@
/**
* Set the local adapter property - scanMode
*
- * @param scanMode the ScanMode to set
+ * @param scanMode the ScanMode to set, valid values are: {
+ * BluetoothAdapter.SCAN_MODE_NONE,
+ * BluetoothAdapter.SCAN_MODE_CONNECTABLE,
+ * BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE,
+ * }
*/
boolean setScanMode(int scanMode) {
+ addScanChangeLog(scanMode);
synchronized (mObject) {
return mService.setAdapterPropertyNative(AbstractionLayer.BT_PROPERTY_ADAPTER_SCAN_MODE,
- Utils.intToByteArray(scanMode));
+ Utils.intToByteArray(AdapterService.convertScanModeToHal(scanMode)));
}
}
+ private void addScanChangeLog(int scanMode) {
+ String time = Utils.getLocalTimeString();
+ String uidPid = Utils.getUidPidString();
+ String scanModeString = dumpScanMode(scanMode);
+
+ mScanModeChanges.add(time + " (" + uidPid + ") " + scanModeString);
+ }
+
/**
* @return the mUuids
*/
@@ -694,13 +714,14 @@
if (state == BluetoothProfile.STATE_CONNECTING) {
BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_DEVICE_NAME_REPORTED,
mService.getMetricId(device), device.getName());
+ MetricsLogger.getInstance().logSanitizedBluetoothDeviceName(device.getName());
}
Log.d(TAG,
"PROFILE_CONNECTION_STATE_CHANGE: profile=" + profile + ", device=" + device + ", "
+ prevState + " -> " + state);
BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_CONNECTION_STATE_CHANGED, state,
0 /* deprecated */, profile, mService.obfuscateAddress(device),
- mService.getMetricId(device), 0);
+ mService.getMetricId(device), 0, -1);
if (!isNormalStateTransition(prevState, state)) {
Log.w(TAG,
@@ -1073,7 +1094,7 @@
mProfilesConnecting = 0;
mProfilesDisconnecting = 0;
// adapterPropertyChangedCallback has already been received. Set the scan mode.
- setScanMode(AbstractionLayer.BT_SCAN_MODE_CONNECTABLE);
+ setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE);
// This keeps NV up-to date on first-boot after flash.
setDiscoverableTimeout(mDiscoverableTimeout);
}
@@ -1083,7 +1104,7 @@
// Sequence BLE_ON to STATE_OFF - that is _complete_ OFF state.
debugLog("onBleDisable");
// Set the scan_mode to NONE (no incoming connections).
- setScanMode(AbstractionLayer.BT_SCAN_MODE_NONE);
+ setScanMode(BluetoothAdapter.SCAN_MODE_NONE);
}
void discoveryStateChangeCallback(int state) {
@@ -1136,6 +1157,12 @@
}
}
writer.println(sb.toString());
+
+ writer.println(" " + "Scan Mode Changes:");
+ for (String log : mScanModeChanges) {
+ writer.println(" " + log);
+ }
+
}
private String dumpDeviceType(int deviceType) {
diff --git a/android/app/src/com/android/bluetooth/btservice/AdapterService.java b/android/app/src/com/android/bluetooth/btservice/AdapterService.java
index c5fb78b..7e36914 100644
--- a/android/app/src/com/android/bluetooth/btservice/AdapterService.java
+++ b/android/app/src/com/android/bluetooth/btservice/AdapterService.java
@@ -50,6 +50,7 @@
import android.bluetooth.BluetoothProtoEnums;
import android.bluetooth.BluetoothSap;
import android.bluetooth.BluetoothServerSocket;
+import android.bluetooth.BluetoothSinkAudioPolicy;
import android.bluetooth.BluetoothSocket;
import android.bluetooth.BluetoothStatusCodes;
import android.bluetooth.BluetoothUuid;
@@ -300,6 +301,7 @@
private ActiveDeviceManager mActiveDeviceManager;
private DatabaseManager mDatabaseManager;
private SilenceDeviceManager mSilenceDeviceManager;
+ private CompanionManager mBtCompanionManager;
private AppOpsManager mAppOps;
private BluetoothSocketManagerBinder mBluetoothSocketManagerBinder;
@@ -441,6 +443,7 @@
getAdapterPropertyNative(AbstractionLayer.BT_PROPERTY_LOCAL_IO_CAPS_BLE);
getAdapterPropertyNative(AbstractionLayer.BT_PROPERTY_DYNAMIC_AUDIO_BUFFER);
mAdapterStateMachine.sendMessage(AdapterState.BREDR_STARTED);
+ mBtCompanionManager.loadCompanionInfo();
}
break;
case BluetoothAdapter.STATE_OFF:
@@ -542,6 +545,8 @@
Looper.getMainLooper());
mSilenceDeviceManager.start();
+ mBtCompanionManager = new CompanionManager(this, new ServiceFactory());
+
mBluetoothSocketManagerBinder = new BluetoothSocketManagerBinder(this);
mActivityAttributionService = new ActivityAttributionService();
@@ -712,7 +717,7 @@
void stopProfileServices() {
// Make sure to stop classic background tasks now
cancelDiscoveryNative();
- mAdapterProperties.setScanMode(AbstractionLayer.BT_SCAN_MODE_NONE);
+ mAdapterProperties.setScanMode(BluetoothAdapter.SCAN_MODE_NONE);
Class[] supportedProfileServices = Config.getSupportedProfiles();
// TODO(b/228875190): GATT is assumed supported. If we support no profiles then just move on
@@ -1557,6 +1562,32 @@
}
/**
+ * Get an metadata of given device and key
+ *
+ * @param device Bluetooth device
+ * @param key Metadata key
+ * @param value Metadata value
+ * @return if metadata is set successfully
+ */
+ public boolean setMetadata(BluetoothDevice device, int key, byte[] value) {
+ if (value == null || value.length > BluetoothDevice.METADATA_MAX_LENGTH) {
+ return false;
+ }
+ return mDatabaseManager.setCustomMeta(device, key, value);
+ }
+
+ /**
+ * Get an metadata of given device and key
+ *
+ * @param device Bluetooth device
+ * @param key Metadata key
+ * @return value of given device and key combination
+ */
+ public byte[] getMetadata(BluetoothDevice device, int key) {
+ return mDatabaseManager.getCustomMeta(device, key);
+ }
+
+ /**
* Handlers for incoming service calls
*/
private AdapterServiceBinder mBinder;
@@ -1967,7 +1998,7 @@
}
enforceBluetoothPrivilegedPermission(service);
- return service.mAdapterProperties.setScanMode(convertScanModeToHal(mode))
+ return service.mAdapterProperties.setScanMode(mode)
? BluetoothStatusCodes.SUCCESS : BluetoothStatusCodes.ERROR_UNKNOWN;
}
@@ -3171,6 +3202,10 @@
service.mBluetoothKeystoreService.factoryReset();
}
+ if (service.mBtCompanionManager != null) {
+ service.mBtCompanionManager.factoryReset();
+ }
+
return service.factoryResetNative();
}
@@ -3636,6 +3671,76 @@
}
@Override
+ public void isRequestAudioPolicyAsSinkSupported(BluetoothDevice device,
+ AttributionSource source, SynchronousResultReceiver receiver) {
+ try {
+ receiver.send(isRequestAudioPolicyAsSinkSupported(device, source));
+ } catch (RuntimeException e) {
+ receiver.propagateException(e);
+ }
+ }
+ private int isRequestAudioPolicyAsSinkSupported(BluetoothDevice device,
+ AttributionSource source) {
+ AdapterService service = getService();
+ if (service == null
+ || !callerIsSystemOrActiveOrManagedUser(service, TAG,
+ "isRequestAudioPolicyAsSinkSupported")
+ || !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
+ return BluetoothStatusCodes.FEATURE_NOT_CONFIGURED;
+ }
+ enforceBluetoothPrivilegedPermission(service);
+ return service.isRequestAudioPolicyAsSinkSupported(device);
+ }
+
+ @Override
+ public void requestAudioPolicyAsSink(BluetoothDevice device,
+ BluetoothSinkAudioPolicy policies, AttributionSource source,
+ SynchronousResultReceiver receiver) {
+ try {
+ receiver.send(requestAudioPolicyAsSink(device, policies, source));
+ } catch (RuntimeException e) {
+ receiver.propagateException(e);
+ }
+ }
+ private int requestAudioPolicyAsSink(BluetoothDevice device,
+ BluetoothSinkAudioPolicy policies, AttributionSource source) {
+ AdapterService service = getService();
+ if (service == null) {
+ return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED;
+ } else if (!callerIsSystemOrActiveOrManagedUser(service,
+ TAG, "requestAudioPolicyAsSink")) {
+ return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ALLOWED;
+ } else if (!Utils.checkConnectPermissionForDataDelivery(
+ service, source, TAG)) {
+ return BluetoothStatusCodes.ERROR_MISSING_BLUETOOTH_CONNECT_PERMISSION;
+ }
+ enforceBluetoothPrivilegedPermission(service);
+ return service.requestAudioPolicyAsSink(device, policies);
+ }
+
+ @Override
+ public void getRequestedAudioPolicyAsSink(BluetoothDevice device,
+ AttributionSource source, SynchronousResultReceiver receiver) {
+ try {
+ receiver.send(getRequestedAudioPolicyAsSink(device, source));
+ } catch (RuntimeException e) {
+ receiver.propagateException(e);
+ }
+ }
+ private BluetoothSinkAudioPolicy getRequestedAudioPolicyAsSink(BluetoothDevice device,
+ AttributionSource source) {
+ AdapterService service = getService();
+ if (service == null
+ || !callerIsSystemOrActiveOrManagedUser(service,
+ TAG, "getRequestedAudioPolicyAsSink")
+ || !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) {
+ return null;
+ }
+ enforceBluetoothPrivilegedPermission(service);
+ return service.getRequestedAudioPolicyAsSink(device);
+ }
+
+ @Override
public void requestActivityInfo(IBluetoothActivityEnergyInfoListener listener,
AttributionSource source) {
BluetoothActivityEnergyInfo info = reportActivityInfo(source);
@@ -4863,7 +4968,7 @@
source.getUid(), source.getPackageName(), deviceAddress);
}
- private static int convertScanModeToHal(int mode) {
+ static int convertScanModeToHal(int mode) {
switch (mode) {
case BluetoothAdapter.SCAN_MODE_NONE:
return AbstractionLayer.BT_SCAN_MODE_NONE;
@@ -5458,6 +5563,84 @@
return getMetricIdNative(Utils.getByteAddress(device));
}
+ public CompanionManager getCompanionManager() {
+ return mBtCompanionManager;
+ }
+
+ /**
+ * Call for the AdapterService receives bond state change
+ *
+ * @param device Bluetooth device
+ * @param state bond state
+ */
+ public void onBondStateChanged(BluetoothDevice device, int state) {
+ if (mBtCompanionManager != null) {
+ mBtCompanionManager.onBondStateChanged(device, state);
+ }
+ }
+
+ /**
+ * Get audio policy feature support status
+ *
+ * @param device Bluetooth device to be checked for audio policy support
+ * @return int status of the remote support for audio policy feature
+ */
+ public int isRequestAudioPolicyAsSinkSupported(BluetoothDevice device) {
+ if (mHeadsetClientService != null) {
+ return mHeadsetClientService.getAudioPolicyRemoteSupported(device);
+ } else {
+ Log.e(TAG, "No audio transport connected");
+ return BluetoothStatusCodes.FEATURE_NOT_CONFIGURED;
+ }
+ }
+
+ /**
+ * Set audio policy for remote device
+ *
+ * @param device Bluetooth device to be set policy for
+ * @return int result status for requestAudioPolicyAsSink API
+ */
+ public int requestAudioPolicyAsSink(BluetoothDevice device, BluetoothSinkAudioPolicy policies) {
+ DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
+ if (deviceProp == null) {
+ return BluetoothStatusCodes.ERROR_DEVICE_NOT_BONDED;
+ }
+
+ if (mHeadsetClientService != null) {
+ if (isRequestAudioPolicyAsSinkSupported(device)
+ != BluetoothStatusCodes.FEATURE_SUPPORTED) {
+ throw new UnsupportedOperationException(
+ "Request Audio Policy As Sink not supported");
+ }
+ deviceProp.setHfAudioPolicyForRemoteAg(policies);
+ mHeadsetClientService.setAudioPolicy(device, policies);
+ return BluetoothStatusCodes.SUCCESS;
+ } else {
+ Log.e(TAG, "HeadsetClient not connected");
+ return BluetoothStatusCodes.ERROR_PROFILE_NOT_CONNECTED;
+ }
+ }
+
+ /**
+ * Get audio policy for remote device
+ *
+ * @param device Bluetooth device to be set policy for
+ * @return {@link BluetoothSinkAudioPolicy} policy stored for the device
+ */
+ public BluetoothSinkAudioPolicy getRequestedAudioPolicyAsSink(BluetoothDevice device) {
+ DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device);
+ if (deviceProp == null) {
+ return null;
+ }
+
+ if (mHeadsetClientService != null) {
+ return deviceProp.getHfAudioPolicyForRemoteAg();
+ } else {
+ Log.e(TAG, "HeadsetClient not connected");
+ return null;
+ }
+ }
+
/**
* Allow audio low latency
*
diff --git a/android/app/src/com/android/bluetooth/btservice/BluetoothSocketManagerBinder.java b/android/app/src/com/android/bluetooth/btservice/BluetoothSocketManagerBinder.java
index 2187a91..13a9f4c 100644
--- a/android/app/src/com/android/bluetooth/btservice/BluetoothSocketManagerBinder.java
+++ b/android/app/src/com/android/bluetooth/btservice/BluetoothSocketManagerBinder.java
@@ -17,6 +17,7 @@
package com.android.bluetooth.btservice;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothSocket;
import android.bluetooth.IBluetoothSocketManager;
import android.os.Binder;
import android.os.ParcelFileDescriptor;
@@ -49,13 +50,17 @@
return null;
}
- return marshalFd(mService.connectSocketNative(
- Utils.getBytesFromAddress(device.getAddress()),
- type,
- Utils.uuidToByteArray(uuid),
- port,
- flag,
- Binder.getCallingUid()));
+ return marshalFd(
+ mService.connectSocketNative(
+ Utils.getBytesFromAddress(
+ type == BluetoothSocket.TYPE_L2CAP_LE
+ ? device.getAddress()
+ : mService.getIdentityAddress(device.getAddress())),
+ type,
+ Utils.uuidToByteArray(uuid),
+ port,
+ flag,
+ Binder.getCallingUid()));
}
@Override
diff --git a/android/app/src/com/android/bluetooth/btservice/BondStateMachine.java b/android/app/src/com/android/bluetooth/btservice/BondStateMachine.java
index 713545f..c6633da 100644
--- a/android/app/src/com/android/bluetooth/btservice/BondStateMachine.java
+++ b/android/app/src/com/android/bluetooth/btservice/BondStateMachine.java
@@ -470,6 +470,7 @@
if (newState == BluetoothDevice.BOND_NONE) {
intent.putExtra(BluetoothDevice.EXTRA_UNBOND_REASON, reason);
}
+ mAdapterService.onBondStateChanged(device, newState);
mAdapterService.sendBroadcastAsUser(intent, UserHandle.ALL, BLUETOOTH_CONNECT,
Utils.getTempAllowlistBroadcastOptions());
infoLog("Bond State Change Intent:" + device + " " + state2str(oldState) + " => "
diff --git a/android/app/src/com/android/bluetooth/btservice/CompanionManager.java b/android/app/src/com/android/bluetooth/btservice/CompanionManager.java
new file mode 100644
index 0000000..64aae78
--- /dev/null
+++ b/android/app/src/com/android/bluetooth/btservice/CompanionManager.java
@@ -0,0 +1,403 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.btservice;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.SystemProperties;
+import android.util.Log;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.android.bluetooth.R;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ A CompanionManager to specify parameters between companion devices and regular devices.
+
+ 1. A paired device is recognized as a companion device if its METADATA_SOFTWARE_VERSION is
+ set to BluetoothDevice.COMPANION_TYPE_PRIMARY or BluetoothDevice.COMPANION_TYPE_SECONDARY.
+ 2. Only can have one companion device at a time.
+ 3. Remove bond does not remove the companion device record.
+ 4. Factory reset Bluetooth removes the companion device.
+ 5. Companion device has individual GATT connection parameters.
+*/
+
+public class CompanionManager {
+ private static final String TAG = "BluetoothCompanionManager";
+
+ private BluetoothDevice mCompanionDevice;
+ private int mCompanionType;
+
+ private final int[] mGattConnHighPrimary;
+ private final int[] mGattConnBalancePrimary;
+ private final int[] mGattConnLowPrimary;
+ private final int[] mGattConnHighSecondary;
+ private final int[] mGattConnBalanceSecondary;
+ private final int[] mGattConnLowSecondary;
+ private final int[] mGattConnHighDefault;
+ private final int[] mGattConnBalanceDefault;
+ private final int[] mGattConnLowDefault;
+
+ @VisibleForTesting static final int COMPANION_TYPE_NONE = 0;
+ @VisibleForTesting static final int COMPANION_TYPE_PRIMARY = 1;
+ @VisibleForTesting static final int COMPANION_TYPE_SECONDARY = 2;
+
+ public static final int GATT_CONN_INTERVAL_MIN = 0;
+ public static final int GATT_CONN_INTERVAL_MAX = 1;
+ public static final int GATT_CONN_LATENCY = 2;
+
+ @VisibleForTesting static final String COMPANION_INFO = "bluetooth_companion_info";
+ @VisibleForTesting static final String COMPANION_DEVICE_KEY = "companion_device";
+ @VisibleForTesting static final String COMPANION_TYPE_KEY = "companion_type";
+
+ static final String PROPERTY_HIGH_MIN_INTERVAL = "bluetooth.gatt.high_priority.min_interval";
+ static final String PROPERTY_HIGH_MAX_INTERVAL = "bluetooth.gatt.high_priority.max_interval";
+ static final String PROPERTY_HIGH_LATENCY = "bluetooth.gatt.high_priority.latency";
+ static final String PROPERTY_BALANCED_MIN_INTERVAL =
+ "bluetooth.gatt.balanced_priority.min_interval";
+ static final String PROPERTY_BALANCED_MAX_INTERVAL =
+ "bluetooth.gatt.balanced_priority.max_interval";
+ static final String PROPERTY_BALANCED_LATENCY = "bluetooth.gatt.balanced_priority.latency";
+ static final String PROPERTY_LOW_MIN_INTERVAL = "bluetooth.gatt.low_priority_min.interval";
+ static final String PROPERTY_LOW_MAX_INTERVAL = "bluetooth.gatt.low_priority_max.interval";
+ static final String PROPERTY_LOW_LATENCY = "bluetooth.gatt.low_priority.latency";
+ static final String PROPERTY_SUFFIX_PRIMARY = ".primary";
+ static final String PROPERTY_SUFFIX_SECONDARY = ".secondary";
+
+ private final AdapterService mAdapterService;
+ private final BluetoothAdapter mAdapter = BluetoothAdapter.getDefaultAdapter();
+ private final Set<BluetoothDevice> mMetadataListeningDevices = new HashSet<>();
+
+ public CompanionManager(AdapterService service, ServiceFactory factory) {
+ mAdapterService = service;
+
+ mGattConnHighDefault = new int[] {
+ getGattConfig(PROPERTY_HIGH_MIN_INTERVAL,
+ R.integer.gatt_high_priority_min_interval),
+ getGattConfig(PROPERTY_HIGH_MAX_INTERVAL,
+ R.integer.gatt_high_priority_max_interval),
+ getGattConfig(PROPERTY_HIGH_LATENCY,
+ R.integer.gatt_high_priority_latency)};
+ mGattConnBalanceDefault = new int[] {
+ getGattConfig(PROPERTY_BALANCED_MIN_INTERVAL,
+ R.integer.gatt_balanced_priority_min_interval),
+ getGattConfig(PROPERTY_BALANCED_MAX_INTERVAL,
+ R.integer.gatt_balanced_priority_max_interval),
+ getGattConfig(PROPERTY_BALANCED_LATENCY,
+ R.integer.gatt_balanced_priority_latency)};
+ mGattConnLowDefault = new int[] {
+ getGattConfig(PROPERTY_LOW_MIN_INTERVAL, R.integer.gatt_low_power_min_interval),
+ getGattConfig(PROPERTY_LOW_MAX_INTERVAL, R.integer.gatt_low_power_max_interval),
+ getGattConfig(PROPERTY_LOW_LATENCY, R.integer.gatt_low_power_latency)};
+
+ mGattConnHighPrimary = new int[] {
+ getGattConfig(PROPERTY_HIGH_MIN_INTERVAL + PROPERTY_SUFFIX_PRIMARY,
+ R.integer.gatt_high_priority_min_interval_primary),
+ getGattConfig(PROPERTY_HIGH_MAX_INTERVAL + PROPERTY_SUFFIX_PRIMARY,
+ R.integer.gatt_high_priority_max_interval_primary),
+ getGattConfig(PROPERTY_HIGH_LATENCY + PROPERTY_SUFFIX_PRIMARY,
+ R.integer.gatt_high_priority_latency_primary)};
+ mGattConnBalancePrimary = new int[] {
+ getGattConfig(PROPERTY_BALANCED_MIN_INTERVAL + PROPERTY_SUFFIX_PRIMARY,
+ R.integer.gatt_balanced_priority_min_interval_primary),
+ getGattConfig(PROPERTY_BALANCED_MAX_INTERVAL + PROPERTY_SUFFIX_PRIMARY,
+ R.integer.gatt_balanced_priority_max_interval_primary),
+ getGattConfig(PROPERTY_BALANCED_LATENCY + PROPERTY_SUFFIX_PRIMARY,
+ R.integer.gatt_balanced_priority_latency_primary)};
+ mGattConnLowPrimary = new int[] {
+ getGattConfig(PROPERTY_LOW_MIN_INTERVAL + PROPERTY_SUFFIX_PRIMARY,
+ R.integer.gatt_low_power_min_interval_primary),
+ getGattConfig(PROPERTY_LOW_MAX_INTERVAL + PROPERTY_SUFFIX_PRIMARY,
+ R.integer.gatt_low_power_max_interval_primary),
+ getGattConfig(PROPERTY_LOW_LATENCY + PROPERTY_SUFFIX_PRIMARY,
+ R.integer.gatt_low_power_latency_primary)};
+
+ mGattConnHighSecondary = new int[] {
+ getGattConfig(PROPERTY_HIGH_MIN_INTERVAL + PROPERTY_SUFFIX_SECONDARY,
+ R.integer.gatt_high_priority_min_interval_secondary),
+ getGattConfig(PROPERTY_HIGH_MAX_INTERVAL + PROPERTY_SUFFIX_SECONDARY,
+ R.integer.gatt_high_priority_max_interval_secondary),
+ getGattConfig(PROPERTY_HIGH_LATENCY + PROPERTY_SUFFIX_SECONDARY,
+ R.integer.gatt_high_priority_latency_secondary)};
+ mGattConnBalanceSecondary = new int[] {
+ getGattConfig(PROPERTY_BALANCED_MIN_INTERVAL + PROPERTY_SUFFIX_SECONDARY,
+ R.integer.gatt_balanced_priority_min_interval_secondary),
+ getGattConfig(PROPERTY_BALANCED_MAX_INTERVAL + PROPERTY_SUFFIX_SECONDARY,
+ R.integer.gatt_balanced_priority_max_interval_secondary),
+ getGattConfig(PROPERTY_BALANCED_LATENCY + PROPERTY_SUFFIX_SECONDARY,
+ R.integer.gatt_balanced_priority_latency_secondary)};
+ mGattConnLowSecondary = new int[] {
+ getGattConfig(PROPERTY_LOW_MIN_INTERVAL + PROPERTY_SUFFIX_SECONDARY,
+ R.integer.gatt_low_power_min_interval_secondary),
+ getGattConfig(PROPERTY_LOW_MAX_INTERVAL + PROPERTY_SUFFIX_SECONDARY,
+ R.integer.gatt_low_power_max_interval_secondary),
+ getGattConfig(PROPERTY_LOW_LATENCY + PROPERTY_SUFFIX_SECONDARY,
+ R.integer.gatt_low_power_latency_secondary)};
+ }
+
+ private int getGattConfig(String property, int resId) {
+ return SystemProperties.getInt(property, mAdapterService.getResources().getInteger(resId));
+ }
+
+ void loadCompanionInfo() {
+ synchronized (mMetadataListeningDevices) {
+ String address = getCompanionPreferences().getString(COMPANION_DEVICE_KEY, "");
+
+ try {
+ mCompanionDevice = mAdapter.getRemoteDevice(address);
+ mCompanionType = getCompanionPreferences().getInt(
+ COMPANION_TYPE_KEY, COMPANION_TYPE_NONE);
+ } catch (IllegalArgumentException e) {
+ mCompanionDevice = null;
+ mCompanionType = COMPANION_TYPE_NONE;
+ }
+ }
+
+ if (mCompanionDevice == null) {
+ // We don't have any companion phone registered, try look from the bonded devices
+ for (BluetoothDevice device : mAdapter.getBondedDevices()) {
+ byte[] metadata = mAdapterService.getMetadata(device,
+ BluetoothDevice.METADATA_SOFTWARE_VERSION);
+ if (metadata == null) {
+ continue;
+ }
+ String valueStr = new String(metadata);
+ if ((valueStr.equals(BluetoothDevice.COMPANION_TYPE_PRIMARY)
+ || valueStr.equals(BluetoothDevice.COMPANION_TYPE_SECONDARY))) {
+ // found the companion device, store and unregister all listeners
+ Log.i(TAG, "Found companion device from the database!");
+ setCompanionDevice(device, valueStr);
+ break;
+ }
+ registerMetadataListener(device);
+ }
+ }
+ Log.i(TAG, "Companion device is " + mCompanionDevice + ", type=" + mCompanionType);
+ }
+
+ final BluetoothAdapter.OnMetadataChangedListener mMetadataListener =
+ new BluetoothAdapter.OnMetadataChangedListener() {
+ @Override
+ public void onMetadataChanged(BluetoothDevice device, int key, byte[] value) {
+ String valueStr = new String(value);
+ Log.d(TAG, String.format("Metadata updated in Device %s: %d = %s.", device,
+ key, value == null ? null : valueStr));
+ if (key == BluetoothDevice.METADATA_SOFTWARE_VERSION
+ && (valueStr.equals(BluetoothDevice.COMPANION_TYPE_PRIMARY)
+ || valueStr.equals(BluetoothDevice.COMPANION_TYPE_SECONDARY))) {
+ setCompanionDevice(device, valueStr);
+ }
+ }
+ };
+
+ private void setCompanionDevice(BluetoothDevice companionDevice, String type) {
+ synchronized (mMetadataListeningDevices) {
+ Log.i(TAG, "setCompanionDevice: " + companionDevice + ", type=" + type);
+ mCompanionDevice = companionDevice;
+ mCompanionType = type.equals(BluetoothDevice.COMPANION_TYPE_PRIMARY)
+ ? COMPANION_TYPE_PRIMARY : COMPANION_TYPE_SECONDARY;
+
+ // unregister all metadata listeners
+ for (BluetoothDevice device : mMetadataListeningDevices) {
+ try {
+ mAdapter.removeOnMetadataChangedListener(device, mMetadataListener);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "failed to unregister metadata listener for "
+ + device + " " + e);
+ }
+ }
+ mMetadataListeningDevices.clear();
+
+ SharedPreferences.Editor pref = getCompanionPreferences().edit();
+ pref.putString(COMPANION_DEVICE_KEY, mCompanionDevice.getAddress());
+ pref.putInt(COMPANION_TYPE_KEY, mCompanionType);
+ pref.apply();
+ }
+ }
+
+ private SharedPreferences getCompanionPreferences() {
+ return mAdapterService.getSharedPreferences(COMPANION_INFO, Context.MODE_PRIVATE);
+ }
+
+ /**
+ * Bond state change event from the AdapterService
+ *
+ * @param device the Bluetooth device
+ * @param state the new Bluetooth bond state of the device
+ */
+ public void onBondStateChanged(BluetoothDevice device, int state) {
+ synchronized (mMetadataListeningDevices) {
+ if (mCompanionDevice != null) {
+ // We already have the companion device, do not care bond state change any more.
+ return;
+ }
+ switch (state) {
+ case BluetoothDevice.BOND_BONDING:
+ registerMetadataListener(device);
+ break;
+ case BluetoothDevice.BOND_NONE:
+ removeMetadataListener(device);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ private void registerMetadataListener(BluetoothDevice device) {
+ synchronized (mMetadataListeningDevices) {
+ Log.d(TAG, "register metadata listener: " + device);
+ try {
+ mAdapter.addOnMetadataChangedListener(
+ device, mAdapterService.getMainExecutor(), mMetadataListener);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "failed to register metadata listener for "
+ + device + " " + e);
+ }
+ mMetadataListeningDevices.add(device);
+ }
+ }
+
+ private void removeMetadataListener(BluetoothDevice device) {
+ synchronized (mMetadataListeningDevices) {
+ if (!mMetadataListeningDevices.contains(device)) return;
+
+ Log.d(TAG, "remove metadata listener: " + device);
+ try {
+ mAdapter.removeOnMetadataChangedListener(device, mMetadataListener);
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "failed to unregister metadata listener for "
+ + device + " " + e);
+ }
+ mMetadataListeningDevices.remove(device);
+ }
+ }
+
+
+ /**
+ * Method to get the stored companion device
+ *
+ * @return the companion Bluetooth device
+ */
+ public BluetoothDevice getCompanionDevice() {
+ return mCompanionDevice;
+ }
+
+ /**
+ * Method to check whether it is a companion device
+ *
+ * @param address the address of the device
+ * @return true if the address is a companion device, otherwise false
+ */
+ public boolean isCompanionDevice(String address) {
+ try {
+ return isCompanionDevice(mAdapter.getRemoteDevice(address));
+ } catch (IllegalArgumentException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Method to check whether it is a companion device
+ *
+ * @param device the Bluetooth device
+ * @return true if the device is a companion device, otherwise false
+ */
+ public boolean isCompanionDevice(BluetoothDevice device) {
+ if (device == null) return false;
+ return device.equals(mCompanionDevice);
+ }
+
+ /**
+ * Method to reset the stored companion info
+ */
+ public void factoryReset() {
+ synchronized (mMetadataListeningDevices) {
+ mCompanionDevice = null;
+ mCompanionType = COMPANION_TYPE_NONE;
+
+ SharedPreferences.Editor pref = getCompanionPreferences().edit();
+ pref.remove(COMPANION_DEVICE_KEY);
+ pref.remove(COMPANION_TYPE_KEY);
+ pref.apply();
+ }
+ }
+
+ /**
+ * Gets the GATT connection parameters of the device
+ *
+ * @param address the address of the Bluetooth device
+ * @param type type of the parameter, can be GATT_CONN_INTERVAL_MIN, GATT_CONN_INTERVAL_MAX
+ * or GATT_CONN_LATENCY
+ * @param priority the priority of the connection, can be
+ * BluetoothGatt.CONNECTION_PRIORITY_HIGH, BluetoothGatt.CONNECTION_PRIORITY_LOW_POWER or
+ * BluetoothGatt.CONNECTION_PRIORITY_BALANCED
+ * @return the connection parameter in integer
+ */
+ public int getGattConnParameters(String address, int type, int priority) {
+ int companionType = isCompanionDevice(address) ? mCompanionType : COMPANION_TYPE_NONE;
+ int parameter;
+ switch (companionType) {
+ case COMPANION_TYPE_PRIMARY:
+ parameter = getGattConnParameterPrimary(type, priority);
+ break;
+ case COMPANION_TYPE_SECONDARY:
+ parameter = getGattConnParameterSecondary(type, priority);
+ break;
+ default:
+ parameter = getGattConnParameterDefault(type, priority);
+ break;
+ }
+ return parameter;
+ }
+
+ private int getGattConnParameterPrimary(int type, int priority) {
+ switch (priority) {
+ case BluetoothGatt.CONNECTION_PRIORITY_HIGH:
+ return mGattConnHighPrimary[type];
+ case BluetoothGatt.CONNECTION_PRIORITY_LOW_POWER:
+ return mGattConnLowPrimary[type];
+ }
+ return mGattConnBalancePrimary[type];
+ }
+
+ private int getGattConnParameterSecondary(int type, int priority) {
+ switch (priority) {
+ case BluetoothGatt.CONNECTION_PRIORITY_HIGH:
+ return mGattConnHighSecondary[type];
+ case BluetoothGatt.CONNECTION_PRIORITY_LOW_POWER:
+ return mGattConnLowSecondary[type];
+ }
+ return mGattConnBalanceSecondary[type];
+ }
+
+ private int getGattConnParameterDefault(int type, int mode) {
+ switch (mode) {
+ case BluetoothGatt.CONNECTION_PRIORITY_HIGH:
+ return mGattConnHighDefault[type];
+ case BluetoothGatt.CONNECTION_PRIORITY_LOW_POWER:
+ return mGattConnLowDefault[type];
+ }
+ return mGattConnBalanceDefault[type];
+ }
+}
diff --git a/android/app/src/com/android/bluetooth/btservice/DeviceBloomfilterGenerator.java b/android/app/src/com/android/bluetooth/btservice/DeviceBloomfilterGenerator.java
new file mode 100644
index 0000000..fcadc29
--- /dev/null
+++ b/android/app/src/com/android/bluetooth/btservice/DeviceBloomfilterGenerator.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.bluetooth.btservice;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * Class to generate a default Device Bloomfilter
+ */
+public class DeviceBloomfilterGenerator {
+ public static final String BLOOM_FILTER_DEFAULT =
+ "01070000013b23cef3cd0e063e5dd15a"
+ + "1a3f14b8a2d6974ab2e5a2d37f2efa97"
+ + "10e526000ae8728c41445c9a1387c123"
+ + "dc63675c0b8da3d365cde65b9edf153d"
+ + "12d3a1ecdf9b78b3b2f86bc294ccf7ea"
+ + "f650e1fa767bcaad3b61520125d38364"
+ + "4cb480d820122ad455e7e422e9bc51fd"
+ + "c442628ed66154916130be24212e4f44"
+ + "efed5a6bc9b7064fa7b2efe86dd4e801"
+ + "72c65b972a7524b370a2bca955429385"
+ + "a405671d87ead027e7cb4080713dd0bf"
+ + "6b440048b14d3d55d41d1f4497143b98"
+ + "3b939c0bb686f026aa3c42df96bcab6a"
+ + "542f9b8b62cd30e76ac744b4a185c7aa"
+ + "3433dd714e95c0f268449c56e904a4d4"
+ + "8bf9d242f99fbbe3e259ee97cbafd1f7"
+ + "65306274f54f67b7dfbf2423b8ef8fd6"
+ + "ee3fca0e2217bb351bd3347c610fa3f7"
+ + "7e5ec2d7b931f657d61784fe59e2516c"
+ + "9c8f8f4bcffe0a247ed16d93347a818a"
+ + "a798320da96f05dbee4c5cf31661121f"
+ + "e0e6e6ce9b657df24a7b8e0b8a34e443"
+ + "79dcb270d856a5431a2b6416464e9327"
+ + "22bb423d6f4e620c33a5f2b1d02f9a8f"
+ + "521f7b49947284af61c0dfc4d0e64ccd"
+ + "1df147ae9999fdf9a3538c0ad83ee7d5"
+ + "d066f0f4759bcb5c46b7c3fd57697b88"
+ + "5a8f82a77d927617a6ea077ff352ff18"
+ + "1ab520f1cc0d73f688b55c37761e0be7"
+ + "e543e33accef44fa212f2b746a239bfd"
+ + "b139e39439ffa76919419b79e4505d17"
+ + "0b412ace6f21c6c34bff54eb2e16429a"
+ + "cbc691d3c17aa9e0590e3d4d3acc9349"
+ + "0d67e7cfbf4dfa9aeaadfead7770af8f"
+ + "fb827e2376d30d027cc949712dbce0f7"
+ + "3bb193dbf9201a59632ba6daf11b8a92"
+ + "6bea34175531df805afd72792c9ebada"
+ + "823a0b677c4ee75d745806a98a4dd754"
+ + "2f5e5f665e3280385a416e94ccd8eb88"
+ + "a949d2daaee0f11c238fed182e1c234c"
+ + "c4021288a0f7f31807b735ea96e3e4fe"
+ + "66d07484a2336971d6ac0e6a79967116"
+ + "cc9eac2921ea51ec822fcc5f90c0f6b4"
+ + "96845542dfe8fbd6299e7d2af66ce423"
+ + "a7346d1af0f5bca2f261e9a247e214a5"
+ + "aecf8d19f2e368d7f0ea9699bc313ccf"
+ + "ccdb8f759d9bf4ee42a49cda2021eba7"
+ + "71add727d5d8cf35143fd4ffc595ee83"
+ + "6d293113cd9ddcea69c009c6f94e4605"
+ + "f96efe314bbad0e5fa449b35e24d1121"
+ + "c1cbcfbacd3bad9759bb5028033ebfc4"
+ + "8ac390285e7b41195fa4c4512cb48bd7"
+ + "2787f52eb8d260e6a9e2b02d32d57c04"
+ + "fd236b933cb365d2ebc99c30fb972ac1"
+ + "fcd1afcf4087c4d612eb1fefc9a03e78"
+ + "de594bc828e3b1aaddb46b7f3d2e0916"
+ + "8c324e1059e2d6b8535c34e4ab05bc13"
+ + "adaf2d75db9d9c8f0891541b573f5782"
+ + "a543f214b34bfdad7373bd6703d4b1ae"
+ + "3793910ad3ccceebda27f714df06c63a"
+ + "94ac90a3044f9c9494ffdc7cb050a750"
+ + "0d647262b98a7f74378f525ba945ddc7"
+ + "a9926b67c553b37ca370ac9016e6b34d"
+ + "5966a6571bc62dfb0fea8906ce4e0739"
+ + "3ff747c356734343bcdc2362baa97e2a"
+ + "eb37244316ac6d0f91e0c6dada3f19d2"
+ + "21f4f309db772bcca9128ee94b11dca5"
+ + "58e678deabfd506f3acf269c0cdd4d66"
+ + "951567041afd88acfa5afff876f024bb"
+ + "7e72db189f9f9e77782aef5f565ceb12"
+ + "1b9cea8200c797bf46f9e086bb6c45c7"
+ + "2a8a7f521523158d005ba13f72866e4b"
+ + "281abb7c01bc16e666b3d9c49ac4ef8a"
+ + "d45b4a63a2d8318cfd6387fa59fc9c1e"
+ + "a7f753d4a2a12a9e802ecaf24ded9075"
+ + "4c476cb2d1f547ecabe06180471cf5b2"
+ + "18099f595df1f96eb9bb301da60853cb"
+ + "3db3ee16dc09f5c167632cb742f9b631"
+ + "35ebf72aaad9f8fbd44f15d9ba77b7c2"
+ + "9bc2873378bd433c0d27258dbf095c75"
+ + "dd7b4ed56b2db02331a5b3817473c6b5"
+ + "b3228749bf1fa16cd88903276b12ac9d"
+ + "949042a04c364725f27644fd082e8e1b"
+ + "2bcaa9ac54b170a67862fd3325e09896"
+ + "bdf499eb1a933d255bb7bd58011379a6"
+ + "20da77c55a7484c0aa19681a8fb71b8b"
+ + "5f10efa2cbaf518a071651b899961dcd"
+ + "953d695f8187a0a3249db6afc81492f1"
+ + "03de215ca8af5c62bda273e0c46f6d4e"
+ + "0f4f8025cc52532b7f4c3ef61769326e"
+ + "841c9c775294ddd2aeba8b7fcb7ce8c4"
+ + "66472f0551c905db5d6c7901e51ba435"
+ + "9a42ed96fb170e2b6e933440de8f4b7e"
+ + "7832696368f9c61c46840db11f5e411a"
+ + "d64e2aa300cfd0768fb919f9434f53b0"
+ + "02e9316c926fe1498ea8b8bfb1f87943"
+ + "6ad5633e004878d47f3102ec93f56737"
+ + "c7a4f0f723f402726f4419f1805b9bcb"
+ + "c25e5536a1356f30756580aa919dcc5f"
+ + "1491bdf6e4639eb56b246e6aa846f721"
+ + "59684a64b413264678df77a633c1c448"
+ + "0ceddd569bf36b61f9fb492ef7ad14dd"
+ + "6b5460e9b267ec1aecf078e3ad180e06"
+ + "35b86c65a0ae236c4cdaed5e48b33525"
+ + "856a70eac296a1744932ee9a91b45821"
+ + "fd7dcaa3e47ef274ed4d34ca440c71bf"
+ + "d9cee7e20b85993d61acb72acaeaf969"
+ + "4f6480d157c4a062a2abf5df87835df8"
+ + "cafbb79aa8f2f2b6b8eae630ff25bdc9"
+ + "5e4df395ce7626882cbb26de3a13d98a"
+ + "b5b7f3bde86c39ab85844cfabaaa9d5e"
+ + "8d6bb9f1c6d644f20c8bf59960efad58"
+ + "ec5071353c1fa7da4a681a650fcbeb8f"
+ + "9cc48389e5e8c0734d2d77126904addb"
+ + "6cf4166e1f4cb964d658a3bba2a2fb33"
+ + "0c16fc9b83f54774b826b38ca96019c8"
+ + "49705809b8656d61044ee19ade74e59f"
+ + "6bce4d414a11bdc1bb76cd096d88dd9d"
+ + "83ca5813bdfa7cc4cd6cefbd090c928d"
+ + "a944ca2012500c510f9462056ee6d99c"
+ + "a76467f9999f4ecf62f7ad1c98eb5914"
+ + "283c354c3fae5b527204983915648b2d"
+ + "4ccda53623e4e1c4eae633f5ed3f18d1"
+ + "3c25d41487014bcc72f3fb69cfe8cdc2"
+ + "d157f899b935ee1501bd8131cd2bdcc1"
+ + "b64c5425562fa6491d24a53047c8720a"
+ + "736be13878c14326035d4b45f319f249"
+ + "cab39e4332aa2e309d264be67c4fb376"
+ + "d3a9698df50497276792384787a9fd1e"
+ + "81c785a7491ec03b7b41625969898df5"
+ + "3456585ebe6db84fd70dcf6cb2914279"
+ + "ccfd1e7fc25d41f5d1020dce935a2eb6"
+ + "7d45641a180f47ab3e6b8cf8f507ef27"
+ + "c8c02c9fbd18519dcfd9adf0fdd4a50b"
+ + "e2c33bd38df85723e9c9763b6ac5a3da"
+ + "70d96dd329a42ca1e7bfed5f7f59e2ab"
+ + "830ba4f968bcf3b7dc2fe6e4d5851ab6"
+ + "360e5265525f153d9fd9ffc333cd946d"
+ + "6c7b035dbb9ee9d1d5a62b1c721481b5"
+ + "0a703f8e9ae4491a83d0fb5e2c72305d"
+ + "3d045f3c43d2db5168af4e1372d5a477"
+ + "ec76b55e3c734fdf9e17d4182ffd5c78"
+ + "0fcf25d709f331a2bd9bd991ab9acbd8"
+ + "50f701c039172dca18db78836de81f96"
+ + "7e75dfa622fe6bdaa7ea896eaab576f8"
+ + "3e9e39148bf5960dc4dc8f3b768415f3"
+ + "67f477cd34cd47ae7b7de6d3332d42d9"
+ + "cf87b883abbd016d668b5f389d72a219"
+ + "c82bdd4f2c2b6768779fe2d74bf01653"
+ + "1d5618d537029d86004bf48f4cc89d16"
+ + "7bdffccd73134c971cff61096877a799"
+ + "9d1bf238fb8c12aae9f02a08b9abdfa5"
+ + "c8d1101a3d1928a7bc63973cd84b62c2"
+ + "9f7c74668d3f203c84b165b5eee84881"
+ + "a8b7a86cf7edf7b2a060c56b75d55286"
+ + "fbf4468a573a7e77e24d32470b95680e"
+ + "7155eeeea7e9522814528e2c414bbf2d"
+ + "fcafa73fcbb3b7a42f19b5f057dd";
+
+ public static byte[] hexStringToByteArray(String s) {
+ int len = s.length();
+ byte[] data = new byte[len / 2];
+ for (int i = 0; i < len; i += 2) {
+ data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ + Character.digit(s.charAt(i + 1), 16));
+ }
+ return data;
+ }
+
+ public static void generateDefaultBloomfilter(String filePath) throws IOException {
+ File outputFile = new File(filePath);
+ outputFile.createNewFile(); // if file already exists will do nothing
+ FileOutputStream fos = new FileOutputStream(filePath);
+ fos.write(hexStringToByteArray(BLOOM_FILTER_DEFAULT));
+ fos.close();
+ }
+}
diff --git a/android/app/src/com/android/bluetooth/btservice/MetricsLogger.java b/android/app/src/com/android/bluetooth/btservice/MetricsLogger.java
index ecbac60..0f3540d 100644
--- a/android/app/src/com/android/bluetooth/btservice/MetricsLogger.java
+++ b/android/app/src/com/android/bluetooth/btservice/MetricsLogger.java
@@ -25,6 +25,15 @@
import com.android.bluetooth.BluetoothMetricsProto.ProfileId;
import com.android.bluetooth.BluetoothStatsLog;
+import com.google.common.hash.BloomFilter;
+import com.google.common.hash.Funnels;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
/**
@@ -32,11 +41,15 @@
*/
public class MetricsLogger {
private static final String TAG = "BluetoothMetricsLogger";
+ private static final String BLOOMFILTER_PATH = "/data/misc/bluetooth/metrics";
+ private static final String BLOOMFILTER_FILE = "/devices";
+ public static final String BLOOMFILTER_FULL_PATH = BLOOMFILTER_PATH + BLOOMFILTER_FILE;
public static final boolean DEBUG = false;
// 6 hours timeout for counter metrics
private static final long BLUETOOTH_COUNTER_METRICS_ACTION_DURATION_MILLIS = 6L * 3600L * 1000L;
+ private static final int MAX_WORDS_ALLOWED_IN_DEVICE_NAME = 7;
private static final HashMap<ProfileId, Integer> sProfileConnectionCounts = new HashMap<>();
@@ -46,6 +59,8 @@
private AlarmManager mAlarmManager = null;
private boolean mInitialized = false;
static final private Object mLock = new Object();
+ private BloomFilter<byte[]> mBloomFilter = null;
+ protected boolean mBloomFilterInitialized = false;
private AlarmManager.OnAlarmListener mOnAlarmListener = new AlarmManager.OnAlarmListener () {
@Override
@@ -70,6 +85,28 @@
return mInitialized;
}
+ public boolean initBloomFilter(String path) {
+ try {
+ File file = new File(path);
+ if (!file.exists()) {
+ Log.w(TAG, "MetricsLogger is creating a new Bloomfilter file");
+ DeviceBloomfilterGenerator.generateDefaultBloomfilter(path);
+ }
+
+ FileInputStream in = new FileInputStream(new File(path));
+ mBloomFilter = BloomFilter.readFrom(in, Funnels.byteArrayFunnel());
+ mBloomFilterInitialized = true;
+ } catch (IOException e) {
+ Log.w(TAG, "MetricsLogger can't read the BloomFilter file");
+ return false;
+ }
+ return true;
+ }
+
+ protected void setBloomfilter(BloomFilter bloomfilter) {
+ mBloomFilter = bloomfilter;
+ }
+
public boolean init(Context context) {
if (mInitialized) {
return false;
@@ -77,6 +114,12 @@
mInitialized = true;
mContext = context;
scheduleDrains();
+ if (!initBloomFilter(BLOOMFILTER_FULL_PATH)) {
+ Log.w(TAG, "MetricsLogger can't initialize the bloomfilter");
+ // The class is for multiple metrics tasks.
+ // We still want to use this class even if the bloomfilter isn't initialized
+ // so still return true here.
+ }
return true;
}
@@ -186,9 +229,70 @@
mAlarmManager = null;
mContext = null;
mInitialized = false;
+ mBloomFilterInitialized = false;
return true;
}
protected void cancelPendingDrain() {
mAlarmManager.cancel(mOnAlarmListener);
}
+
+ protected boolean logSanitizedBluetoothDeviceName(String deviceName) {
+ if (!mBloomFilterInitialized) {
+ return false;
+ }
+
+ // remove more than one spaces in a row
+ deviceName = deviceName.trim().replaceAll(" +", " ");
+ // remove non alphanumeric characters and spaces, and transform to lower cases.
+ String[] words = deviceName.replaceAll(
+ "[^a-zA-Z0-9 ]", "").toLowerCase().split(" ");
+
+ if (words.length > MAX_WORDS_ALLOWED_IN_DEVICE_NAME) {
+ // Validity checking here to avoid excessively long sequences
+ return false;
+ }
+ // find the longest matched substring
+ String matchedString = "";
+ byte[] matchedSha256 = null;
+ for (int start = 0; start < words.length; start++) {
+
+ String toBeMatched = "";
+ for (int end = start; end < words.length; end++) {
+ toBeMatched += words[end];
+ byte[] sha256 = getSha256(toBeMatched);
+ if (sha256 == null) {
+ continue;
+ }
+
+ if (mBloomFilter.mightContain(sha256)
+ && toBeMatched.length() > matchedString.length()) {
+ matchedString = toBeMatched;
+ matchedSha256 = sha256;
+ }
+ }
+ }
+
+ // upload the sha256 of the longest matched string.
+ if (matchedSha256 == null) {
+ return false;
+ }
+ statslogBluetoothDeviceNames(matchedString, matchedSha256);
+ return true;
+ }
+
+ protected void statslogBluetoothDeviceNames(String matchedString, byte[] sha256) {
+ Log.w(TAG, "Uploading sha256 hash of matched bluetooth device name: "
+ + (new String(sha256, StandardCharsets.UTF_8)));
+ }
+
+ protected static byte[] getSha256(String name) {
+ MessageDigest digest = null;
+ try {
+ digest = MessageDigest.getInstance("SHA-256");
+ } catch (NoSuchAlgorithmException e) {
+ Log.w(TAG, "No SHA-256 in MessageDigest");
+ return null;
+ }
+ return digest.digest(name.getBytes(StandardCharsets.UTF_8));
+ }
}
diff --git a/android/app/src/com/android/bluetooth/btservice/PhonePolicy.java b/android/app/src/com/android/bluetooth/btservice/PhonePolicy.java
index 163a3cd..6d2e838 100644
--- a/android/app/src/com/android/bluetooth/btservice/PhonePolicy.java
+++ b/android/app/src/com/android/bluetooth/btservice/PhonePolicy.java
@@ -355,10 +355,12 @@
BluetoothProfile.PAN, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
}
+ boolean isLeAudioProfileAllowed = false;
if ((leAudioService != null) && Utils.arrayContains(uuids,
BluetoothUuid.LE_AUDIO) && (leAudioService.getConnectionPolicy(device)
== BluetoothProfile.CONNECTION_POLICY_UNKNOWN)) {
debugLog("setting le audio profile priority for device " + device);
+ isLeAudioProfileAllowed = true;
mAdapterService.getDatabase().setProfileConnectionPolicy(device,
BluetoothProfile.LE_AUDIO, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
if (mPreferLeAudioOnlyMode) {
@@ -384,9 +386,13 @@
if ((hearingAidService != null) && Utils.arrayContains(uuids,
BluetoothUuid.HEARING_AID) && (hearingAidService.getConnectionPolicy(device)
== BluetoothProfile.CONNECTION_POLICY_UNKNOWN)) {
- debugLog("setting hearing aid profile priority for device " + device);
- mAdapterService.getDatabase().setProfileConnectionPolicy(device,
- BluetoothProfile.HEARING_AID, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ if (isLeAudioProfileAllowed) {
+ debugLog("LE Audio preferred over ASHA for device " + device);
+ } else {
+ debugLog("setting hearing aid profile priority for device " + device);
+ mAdapterService.getDatabase().setProfileConnectionPolicy(device,
+ BluetoothProfile.HEARING_AID, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ }
}
if ((volumeControlService != null) && Utils.arrayContains(uuids,
diff --git a/android/app/src/com/android/bluetooth/btservice/RemoteDevices.java b/android/app/src/com/android/bluetooth/btservice/RemoteDevices.java
index 90d10ee..e748631 100644
--- a/android/app/src/com/android/bluetooth/btservice/RemoteDevices.java
+++ b/android/app/src/com/android/bluetooth/btservice/RemoteDevices.java
@@ -27,6 +27,7 @@
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothHeadsetClient;
import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothSinkAudioPolicy;
import android.bluetooth.IBluetoothConnectionCallback;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -38,6 +39,7 @@
import android.os.Message;
import android.os.ParcelUuid;
import android.os.RemoteException;
+import android.os.SystemProperties;
import android.util.Log;
import com.android.bluetooth.BluetoothStatsLog;
@@ -245,29 +247,28 @@
DeviceProperties getDeviceProperties(BluetoothDevice device) {
synchronized (mDevices) {
- DeviceProperties prop = mDevices.get(device.getAddress());
- if (prop == null) {
- String mainAddress = mDualDevicesMap.get(device.getAddress());
- if (mainAddress != null && mDevices.get(mainAddress) != null) {
- prop = mDevices.get(mainAddress);
- }
+ String address = mDualDevicesMap.get(device.getAddress());
+ // If the device is not in the dual map, use its original address
+ if (address == null || mDevices.get(address) == null) {
+ address = device.getAddress();
}
- return prop;
+ return mDevices.get(address);
}
}
BluetoothDevice getDevice(byte[] address) {
String addressString = Utils.getAddressStringFromByte(address);
- DeviceProperties prop = mDevices.get(addressString);
- if (prop == null) {
- String mainAddress = mDualDevicesMap.get(addressString);
- if (mainAddress != null && mDevices.get(mainAddress) != null) {
- prop = mDevices.get(mainAddress);
- return prop.getDevice();
- }
- return null;
+ String deviceAddress = mDualDevicesMap.get(addressString);
+ // If the device is not in the dual map, use its original address
+ if (deviceAddress == null || mDevices.get(deviceAddress) == null) {
+ deviceAddress = addressString;
}
- return prop.getDevice();
+
+ DeviceProperties prop = mDevices.get(deviceAddress);
+ if (prop != null) {
+ return prop.getDevice();
+ }
+ return null;
}
@VisibleForTesting
@@ -311,6 +312,7 @@
@VisibleForTesting int mBondState;
@VisibleForTesting int mDeviceType;
@VisibleForTesting ParcelUuid[] mUuids;
+ private BluetoothSinkAudioPolicy mAudioPolicy;
DeviceProperties() {
mBondState = BluetoothDevice.BOND_NONE;
@@ -498,6 +500,14 @@
return mIsCoordinatedSetMember;
}
}
+
+ public void setHfAudioPolicyForRemoteAg(BluetoothSinkAudioPolicy policies) {
+ mAudioPolicy = policies;
+ }
+
+ public BluetoothSinkAudioPolicy getHfAudioPolicyForRemoteAg() {
+ return mAudioPolicy;
+ }
}
private void sendUuidIntent(BluetoothDevice device, DeviceProperties prop) {
@@ -754,6 +764,12 @@
errorLog("Device Properties is null for Device:" + device);
return;
}
+ boolean restrict_device_found =
+ SystemProperties.getBoolean("bluetooth.restrict_discovered_device.enabled", false);
+ if (restrict_device_found && (deviceProp.mName == null || deviceProp.mName.isEmpty())) {
+ debugLog("Device name is null or empty: " + device);
+ return;
+ }
Intent intent = new Intent(BluetoothDevice.ACTION_FOUND);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
diff --git a/android/app/src/com/android/bluetooth/btservice/storage/AudioPolicyEntity.java b/android/app/src/com/android/bluetooth/btservice/storage/AudioPolicyEntity.java
new file mode 100644
index 0000000..d5ec422
--- /dev/null
+++ b/android/app/src/com/android/bluetooth/btservice/storage/AudioPolicyEntity.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.btservice.storage;
+
+import android.bluetooth.BluetoothSinkAudioPolicy;
+
+import androidx.room.ColumnInfo;
+import androidx.room.Entity;
+
+@Entity
+class AudioPolicyEntity {
+ @ColumnInfo(name = "call_establish_audio_policy")
+ public int callEstablishAudioPolicy;
+ @ColumnInfo(name = "connecting_time_audio_policy")
+ public int connectingTimeAudioPolicy;
+ @ColumnInfo(name = "in_band_ringtone_audio_policy")
+ public int inBandRingtoneAudioPolicy;
+
+ AudioPolicyEntity() {
+ callEstablishAudioPolicy = BluetoothSinkAudioPolicy.POLICY_UNCONFIGURED;
+ connectingTimeAudioPolicy = BluetoothSinkAudioPolicy.POLICY_UNCONFIGURED;
+ inBandRingtoneAudioPolicy = BluetoothSinkAudioPolicy.POLICY_UNCONFIGURED;
+ }
+
+ AudioPolicyEntity(int callEstablishAudioPolicy, int connectingTimeAudioPolicy,
+ int inBandRingtoneAudioPolicy) {
+ this.callEstablishAudioPolicy = callEstablishAudioPolicy;
+ this.connectingTimeAudioPolicy = connectingTimeAudioPolicy;
+ this.inBandRingtoneAudioPolicy = inBandRingtoneAudioPolicy;
+ }
+
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("callEstablishAudioPolicy=")
+ .append(metadataToString(callEstablishAudioPolicy))
+ .append("|connectingTimeAudioPolicy=")
+ .append(metadataToString(connectingTimeAudioPolicy))
+ .append("|inBandRingtoneAudioPolicy=")
+ .append(metadataToString(inBandRingtoneAudioPolicy));
+
+ return builder.toString();
+ }
+
+ private String metadataToString(int metadata) {
+ return String.valueOf(metadata);
+ }
+}
diff --git a/android/app/src/com/android/bluetooth/btservice/storage/CustomizedMetadataEntity.java b/android/app/src/com/android/bluetooth/btservice/storage/CustomizedMetadataEntity.java
index b3746a2..e2f0765 100644
--- a/android/app/src/com/android/bluetooth/btservice/storage/CustomizedMetadataEntity.java
+++ b/android/app/src/com/android/bluetooth/btservice/storage/CustomizedMetadataEntity.java
@@ -47,6 +47,8 @@
public byte[] spatial_audio;
public byte[] fastpair_customized;
public byte[] le_audio;
+ public byte[] gmcs_cccd;
+ public byte[] gtbs_cccd;
public String toString() {
StringBuilder builder = new StringBuilder();
@@ -103,7 +105,11 @@
.append("|fastpair_customized=")
.append(metadataToString(fastpair_customized))
.append("|le_audio=")
- .append(metadataToString(le_audio));
+ .append(metadataToString(le_audio))
+ .append("|gmcs_cccd=")
+ .append(metadataToString(gmcs_cccd))
+ .append("|gtbs_cccd=")
+ .append(metadataToString(gtbs_cccd));
return builder.toString();
diff --git a/android/app/src/com/android/bluetooth/btservice/storage/DatabaseManager.java b/android/app/src/com/android/bluetooth/btservice/storage/DatabaseManager.java
index fa85049..cd81c9d 100644
--- a/android/app/src/com/android/bluetooth/btservice/storage/DatabaseManager.java
+++ b/android/app/src/com/android/bluetooth/btservice/storage/DatabaseManager.java
@@ -23,6 +23,7 @@
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothProtoEnums;
+import android.bluetooth.BluetoothSinkAudioPolicy;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
@@ -284,6 +285,60 @@
}
/**
+ * Set audio policy metadata to database with requested key
+ */
+ @VisibleForTesting
+ public boolean setAudioPolicyMetadata(BluetoothDevice device,
+ BluetoothSinkAudioPolicy policies) {
+ synchronized (mMetadataCache) {
+ if (device == null) {
+ Log.e(TAG, "setAudioPolicyMetadata: device is null");
+ return false;
+ }
+
+ String address = device.getAddress();
+ if (!mMetadataCache.containsKey(address)) {
+ createMetadata(address, false);
+ }
+ Metadata data = mMetadataCache.get(address);
+ AudioPolicyEntity entity = data.audioPolicyMetadata;
+ entity.callEstablishAudioPolicy = policies.getCallEstablishPolicy();
+ entity.connectingTimeAudioPolicy = policies.getActiveDevicePolicyAfterConnection();
+ entity.inBandRingtoneAudioPolicy = policies.getInBandRingtonePolicy();
+
+ updateDatabase(data);
+ return true;
+ }
+ }
+
+ /**
+ * Get audio policy metadata from database with requested key
+ */
+ @VisibleForTesting
+ public BluetoothSinkAudioPolicy getAudioPolicyMetadata(BluetoothDevice device) {
+ synchronized (mMetadataCache) {
+ if (device == null) {
+ Log.e(TAG, "getAudioPolicyMetadata: device is null");
+ return null;
+ }
+
+ String address = device.getAddress();
+
+ if (!mMetadataCache.containsKey(address)) {
+ Log.d(TAG, "getAudioPolicyMetadata: device " + address + " is not in cache");
+ return null;
+ }
+
+ AudioPolicyEntity entity = mMetadataCache.get(address).audioPolicyMetadata;
+ return new BluetoothSinkAudioPolicy.Builder()
+ .setCallEstablishPolicy(entity.callEstablishAudioPolicy)
+ .setActiveDevicePolicyAfterConnection(entity.connectingTimeAudioPolicy)
+ .setInBandRingtonePolicy(entity.inBandRingtoneAudioPolicy)
+ .build();
+ }
+ }
+
+ /**
* Set the device profile connection policy
*
* @param device {@link BluetoothDevice} wish to set
diff --git a/android/app/src/com/android/bluetooth/btservice/storage/Metadata.java b/android/app/src/com/android/bluetooth/btservice/storage/Metadata.java
index f64f35e..756b6d7 100644
--- a/android/app/src/com/android/bluetooth/btservice/storage/Metadata.java
+++ b/android/app/src/com/android/bluetooth/btservice/storage/Metadata.java
@@ -58,6 +58,25 @@
public long last_active_time;
public boolean is_active_a2dp_device;
+ @Embedded
+ public AudioPolicyEntity audioPolicyMetadata;
+
+ /**
+ * The preferred profile to be used for {@link BluetoothDevice#AUDIO_MODE_OUTPUT_ONLY}. This can
+ * be either {@link BluetoothProfile#A2DP} or {@link BluetoothProfile#LE_AUDIO}. This value is
+ * only used if the remote device supports both A2DP and LE Audio and both transports are
+ * connected and active.
+ */
+ public int preferred_output_only_profile;
+
+ /**
+ * The preferred profile to be used for {@link BluetoothDevice#AUDIO_MODE_DUPLEX}. This can
+ * be either {@link BluetoothProfile#HEADSET} or {@link BluetoothProfile#LE_AUDIO}. This value
+ * is only used if the remote device supports both HFP and LE Audio and both transports are
+ * connected and active.
+ */
+ public int preferred_duplex_profile;
+
Metadata(String address) {
this.address = address;
migrated = false;
@@ -67,6 +86,9 @@
a2dpOptionalCodecsEnabled = BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN;
last_active_time = MetadataDatabase.sCurrentConnectionNumber++;
is_active_a2dp_device = true;
+ audioPolicyMetadata = new AudioPolicyEntity();
+ preferred_output_only_profile = 0;
+ preferred_duplex_profile = 0;
}
/**
@@ -290,6 +312,12 @@
case BluetoothDevice.METADATA_LE_AUDIO:
publicMetadata.le_audio = value;
break;
+ case BluetoothDevice.METADATA_GMCS_CCCD:
+ publicMetadata.gmcs_cccd = value;
+ break;
+ case BluetoothDevice.METADATA_GTBS_CCCD:
+ publicMetadata.gtbs_cccd = value;
+ break;
}
}
@@ -381,6 +409,12 @@
case BluetoothDevice.METADATA_LE_AUDIO:
value = publicMetadata.le_audio;
break;
+ case BluetoothDevice.METADATA_GMCS_CCCD:
+ value = publicMetadata.gmcs_cccd;
+ break;
+ case BluetoothDevice.METADATA_GTBS_CCCD:
+ value = publicMetadata.gtbs_cccd;
+ break;
}
return value;
}
@@ -407,6 +441,8 @@
.append(a2dpOptionalCodecsEnabled)
.append("), custom metadata(")
.append(publicMetadata)
+ .append("), hfp client audio policy(")
+ .append(audioPolicyMetadata)
.append(")}");
return builder.toString();
diff --git a/android/app/src/com/android/bluetooth/btservice/storage/MetadataDatabase.java b/android/app/src/com/android/bluetooth/btservice/storage/MetadataDatabase.java
index a71a548..2776e65 100644
--- a/android/app/src/com/android/bluetooth/btservice/storage/MetadataDatabase.java
+++ b/android/app/src/com/android/bluetooth/btservice/storage/MetadataDatabase.java
@@ -33,7 +33,7 @@
/**
* MetadataDatabase is a Room database stores Bluetooth persistence data
*/
-@Database(entities = {Metadata.class}, version = 114)
+@Database(entities = {Metadata.class}, version = 117)
public abstract class MetadataDatabase extends RoomDatabase {
/**
* The metadata database file name
@@ -67,6 +67,9 @@
.addMigrations(MIGRATION_111_112)
.addMigrations(MIGRATION_112_113)
.addMigrations(MIGRATION_113_114)
+ .addMigrations(MIGRATION_114_115)
+ .addMigrations(MIGRATION_115_116)
+ .addMigrations(MIGRATION_116_117)
.allowMainThreadQueries()
.build();
}
@@ -500,4 +503,67 @@
}
}
};
+
+ @VisibleForTesting
+ static final Migration MIGRATION_114_115 = new Migration(114, 115) {
+ @Override
+ public void migrate(SupportSQLiteDatabase database) {
+ try {
+ database.execSQL(
+ "ALTER TABLE metadata ADD COLUMN `call_establish_audio_policy` "
+ + "INTEGER DEFAULT 0");
+ database.execSQL(
+ "ALTER TABLE metadata ADD COLUMN `connecting_time_audio_policy` "
+ + "INTEGER DEFAULT 0");
+ database.execSQL(
+ "ALTER TABLE metadata ADD COLUMN `in_band_ringtone_audio_policy` "
+ + "INTEGER DEFAULT 0");
+ } catch (SQLException ex) {
+ // Check if user has new schema, but is just missing the version update
+ Cursor cursor = database.query("SELECT * FROM metadata");
+ if (cursor == null
+ || cursor.getColumnIndex("call_establish_audio_policy") == -1) {
+ throw ex;
+ }
+ }
+ }
+ };
+
+ @VisibleForTesting
+ static final Migration MIGRATION_115_116 = new Migration(115, 116) {
+ @Override
+ public void migrate(SupportSQLiteDatabase database) {
+ try {
+ database.execSQL("ALTER TABLE metadata ADD COLUMN `preferred_output_only_profile` "
+ + "INTEGER NOT NULL DEFAULT 0");
+ database.execSQL("ALTER TABLE metadata ADD COLUMN `preferred_duplex_profile` "
+ + "INTEGER NOT NULL DEFAULT 0");
+ } catch (SQLException ex) {
+ // Check if user has new schema, but is just missing the version update
+ Cursor cursor = database.query("SELECT * FROM metadata");
+ if (cursor == null
+ || cursor.getColumnIndex("preferred_output_only_profile") == -1
+ || cursor.getColumnIndex("preferred_duplex_profile") == -1) {
+ throw ex;
+ }
+ }
+ }
+ };
+
+ @VisibleForTesting
+ static final Migration MIGRATION_116_117 = new Migration(116, 117) {
+ @Override
+ public void migrate(SupportSQLiteDatabase database) {
+ try {
+ database.execSQL("ALTER TABLE metadata ADD COLUMN `gmcs_cccd` BLOB");
+ database.execSQL("ALTER TABLE metadata ADD COLUMN `gtbs_cccd` BLOB");
+ } catch (SQLException ex) {
+ // Check if user has new schema, but is just missing the version update
+ Cursor cursor = database.query("SELECT * FROM metadata");
+ if (cursor == null || cursor.getColumnIndex("gmcs_cccd") == -1) {
+ throw ex;
+ }
+ }
+ }
+ };
}
diff --git a/android/app/src/com/android/bluetooth/csip/CsipSetCoordinatorStateMachine.java b/android/app/src/com/android/bluetooth/csip/CsipSetCoordinatorStateMachine.java
index 5945180..062b004 100644
--- a/android/app/src/com/android/bluetooth/csip/CsipSetCoordinatorStateMachine.java
+++ b/android/app/src/com/android/bluetooth/csip/CsipSetCoordinatorStateMachine.java
@@ -47,7 +47,7 @@
static final int CONNECT = 1;
static final int DISCONNECT = 2;
@VisibleForTesting static final int STACK_EVENT = 101;
- private static final int CONNECT_TIMEOUT = 201;
+ @VisibleForTesting static final int CONNECT_TIMEOUT = 201;
// NOTE: the value is not "final" - it is modified in the unit tests
@VisibleForTesting static int sConnectTimeoutMs = 30000; // 30s
diff --git a/android/app/src/com/android/bluetooth/gatt/AdvertiseManager.java b/android/app/src/com/android/bluetooth/gatt/AdvertiseManager.java
index 4ba889d..f1c83b3 100644
--- a/android/app/src/com/android/bluetooth/gatt/AdvertiseManager.java
+++ b/android/app/src/com/android/bluetooth/gatt/AdvertiseManager.java
@@ -31,6 +31,7 @@
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.gatt.GattService.AdvertiserMap;
+import com.android.internal.annotations.VisibleForTesting;
import java.util.Collections;
import java.util.HashMap;
@@ -41,7 +42,8 @@
*
* @hide
*/
-class AdvertiseManager {
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public class AdvertiseManager {
private static final boolean DBG = GattServiceConfig.DBG;
private static final String TAG = GattServiceConfig.TAG_PREFIX + "AdvertiseManager";
diff --git a/android/app/src/com/android/bluetooth/gatt/GattService.java b/android/app/src/com/android/bluetooth/gatt/GattService.java
index 2bcedd7..c8f589b 100644
--- a/android/app/src/com/android/bluetooth/gatt/GattService.java
+++ b/android/app/src/com/android/bluetooth/gatt/GattService.java
@@ -79,6 +79,7 @@
import com.android.bluetooth.btservice.AbstractionLayer;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.BluetoothAdapterProxy;
+import com.android.bluetooth.btservice.CompanionManager;
import com.android.bluetooth.btservice.ProfileService;
import com.android.bluetooth.util.NumberUtils;
import com.android.internal.annotations.VisibleForTesting;
@@ -145,7 +146,8 @@
/**
* The default floor value for LE batch scan report delays greater than 0
*/
- private static final long DEFAULT_REPORT_DELAY_FLOOR = 5000;
+ @VisibleForTesting
+ static final long DEFAULT_REPORT_DELAY_FLOOR = 5000;
// onFoundLost related constants
private static final int ADVT_STATE_ONFOUND = 0;
@@ -274,9 +276,12 @@
private AdapterService mAdapterService;
private BluetoothAdapterProxy mBluetoothAdapterProxy;
- private AdvertiseManager mAdvertiseManager;
- private PeriodicScanManager mPeriodicScanManager;
- private ScanManager mScanManager;
+ @VisibleForTesting
+ AdvertiseManager mAdvertiseManager;
+ @VisibleForTesting
+ PeriodicScanManager mPeriodicScanManager;
+ @VisibleForTesting
+ ScanManager mScanManager;
private AppOpsManager mAppOps;
private CompanionDeviceManager mCompanionManager;
private String mExposureNotificationPackage;
@@ -309,7 +314,8 @@
/**
* Reliable write queue
*/
- private Set<String> mReliableQueue = new HashSet<String>();
+ @VisibleForTesting
+ Set<String> mReliableQueue = new HashSet<String>();
static {
classInitNative();
@@ -2048,7 +2054,7 @@
(status == BluetoothGatt.GATT_SUCCESS), address);
}
statsLogGattConnectionStateChange(
- BluetoothProfile.GATT, address, clientIf, connectionState);
+ BluetoothProfile.GATT, address, clientIf, connectionState, status);
}
void onDisconnected(int clientIf, int connId, int status, String address)
@@ -2083,7 +2089,7 @@
}
statsLogGattConnectionStateChange(
BluetoothProfile.GATT, address, clientIf,
- BluetoothProtoEnums.CONNECTION_STATE_DISCONNECTED);
+ BluetoothProtoEnums.CONNECTION_STATE_DISCONNECTED, status);
}
void onClientPhyUpdate(int connId, int txPhy, int rxPhy, int status) throws RemoteException {
@@ -3431,7 +3437,7 @@
statsLogAppPackage(address, attributionSource.getUid(), clientIf);
statsLogGattConnectionStateChange(
BluetoothProfile.GATT, address, clientIf,
- BluetoothProtoEnums.CONNECTION_STATE_CONNECTING);
+ BluetoothProtoEnums.CONNECTION_STATE_CONNECTING, -1);
gattClientConnectNative(clientIf, address, isDirect, transport, opportunistic, phy);
}
@@ -3448,7 +3454,7 @@
}
statsLogGattConnectionStateChange(
BluetoothProfile.GATT, address, clientIf,
- BluetoothProtoEnums.CONNECTION_STATE_DISCONNECTING);
+ BluetoothProtoEnums.CONNECTION_STATE_DISCONNECTING, -1);
gattClientDisconnectNative(clientIf, address, connId != null ? connId : 0);
}
@@ -3860,33 +3866,21 @@
// Link supervision timeout is measured in N * 10ms
int timeout = 500; // 5s
- switch (connectionPriority) {
- case BluetoothGatt.CONNECTION_PRIORITY_HIGH:
- minInterval = getResources().getInteger(R.integer.gatt_high_priority_min_interval);
- maxInterval = getResources().getInteger(R.integer.gatt_high_priority_max_interval);
- latency = getResources().getInteger(R.integer.gatt_high_priority_latency);
- break;
- case BluetoothGatt.CONNECTION_PRIORITY_LOW_POWER:
- minInterval = getResources().getInteger(R.integer.gatt_low_power_min_interval);
- maxInterval = getResources().getInteger(R.integer.gatt_low_power_max_interval);
- latency = getResources().getInteger(R.integer.gatt_low_power_latency);
- break;
+ CompanionManager manager =
+ AdapterService.getAdapterService().getCompanionManager();
- default:
- // Using the values for CONNECTION_PRIORITY_BALANCED.
- minInterval =
- getResources().getInteger(R.integer.gatt_balanced_priority_min_interval);
- maxInterval =
- getResources().getInteger(R.integer.gatt_balanced_priority_max_interval);
- latency = getResources().getInteger(R.integer.gatt_balanced_priority_latency);
- break;
- }
+ minInterval = manager.getGattConnParameters(
+ address, CompanionManager.GATT_CONN_INTERVAL_MIN, connectionPriority);
+ maxInterval = manager.getGattConnParameters(
+ address, CompanionManager.GATT_CONN_INTERVAL_MAX, connectionPriority);
+ latency = manager.getGattConnParameters(
+ address, CompanionManager.GATT_CONN_LATENCY, connectionPriority);
- if (DBG) {
- Log.d(TAG, "connectionParameterUpdate() - address=" + address + "params="
- + connectionPriority + " interval=" + minInterval + "/" + maxInterval);
- }
+ Log.d(TAG, "connectionParameterUpdate() - address=" + address + " params="
+ + connectionPriority + " interval=" + minInterval + "/" + maxInterval
+ + " timeout=" + timeout);
+
gattConnectionParameterUpdateNative(clientIf, address, minInterval, maxInterval, latency,
timeout, 0, 0);
}
@@ -3901,14 +3895,11 @@
return;
}
- if (DBG) {
- Log.d(TAG, "leConnectionUpdate() - address=" + address + ", intervals="
- + minInterval + "/" + maxInterval + ", latency=" + peripheralLatency
- + ", timeout=" + supervisionTimeout + "msec" + ", min_ce="
- + minConnectionEventLen + ", max_ce=" + maxConnectionEventLen);
+ Log.d(TAG, "leConnectionUpdate() - address=" + address + ", intervals="
+ + minInterval + "/" + maxInterval + ", latency=" + peripheralLatency
+ + ", timeout=" + supervisionTimeout + "msec" + ", min_ce="
+ + minConnectionEventLen + ", max_ce=" + maxConnectionEventLen);
-
- }
gattConnectionParameterUpdateNative(clientIf, address, minInterval, maxInterval,
peripheralLatency, supervisionTimeout,
minConnectionEventLen, maxConnectionEventLen);
@@ -4031,7 +4022,7 @@
app.callback.onServerConnectionState((byte) 0, serverIf, connected, address);
statsLogAppPackage(address, applicationUid, serverIf);
statsLogGattConnectionStateChange(
- BluetoothProfile.GATT_SERVER, address, serverIf, connectionState);
+ BluetoothProfile.GATT_SERVER, address, serverIf, connectionState, -1);
}
void onServerReadCharacteristic(String address, int connId, int transId, int handle, int offset,
@@ -4581,7 +4572,8 @@
* a new ScanSettings object with the report delay being the floor value if the original
* report delay was between 0 and the floor value (exclusive of both)
*/
- private ScanSettings enforceReportDelayFloor(ScanSettings settings) {
+ @VisibleForTesting
+ ScanSettings enforceReportDelayFloor(ScanSettings settings) {
if (settings.getReportDelayMillis() == 0) {
return settings;
}
@@ -4720,16 +4712,18 @@
}
private void statsLogGattConnectionStateChange(
- int profile, String address, int sessionIndex, int connectionState) {
+ int profile, String address, int sessionIndex, int connectionState,
+ int connectionStatus) {
BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(address);
BluetoothStatsLog.write(
BluetoothStatsLog.BLUETOOTH_CONNECTION_STATE_CHANGED, connectionState,
0 /* deprecated */, profile, new byte[0],
- mAdapterService.getMetricId(device), sessionIndex);
+ mAdapterService.getMetricId(device), sessionIndex, connectionStatus);
if (DBG) {
Log.d(TAG, "Gatt Logging: metric_id=" + mAdapterService.getMetricId(device)
+ ", session_index=" + sessionIndex
- + ", connection state=" + connectionState);
+ + ", connection state=" + connectionState
+ + ", connection status=" + connectionStatus);
}
}
diff --git a/android/app/src/com/android/bluetooth/gatt/PeriodicScanManager.java b/android/app/src/com/android/bluetooth/gatt/PeriodicScanManager.java
index f4fdb95..e165f0b 100644
--- a/android/app/src/com/android/bluetooth/gatt/PeriodicScanManager.java
+++ b/android/app/src/com/android/bluetooth/gatt/PeriodicScanManager.java
@@ -28,6 +28,7 @@
import android.util.Log;
import com.android.bluetooth.btservice.AdapterService;
+import com.android.internal.annotations.VisibleForTesting;
import java.util.Collections;
import java.util.HashMap;
@@ -39,7 +40,8 @@
*
* @hide
*/
-class PeriodicScanManager {
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public class PeriodicScanManager {
private static final boolean DBG = GattServiceConfig.DBG;
private static final String TAG = GattServiceConfig.TAG_PREFIX + "SyncManager";
diff --git a/android/app/src/com/android/bluetooth/gatt/ScanFilterQueue.java b/android/app/src/com/android/bluetooth/gatt/ScanFilterQueue.java
index 9919732..d223967 100644
--- a/android/app/src/com/android/bluetooth/gatt/ScanFilterQueue.java
+++ b/android/app/src/com/android/bluetooth/gatt/ScanFilterQueue.java
@@ -39,7 +39,7 @@
public static final int TYPE_LOCAL_NAME = 4;
public static final int TYPE_MANUFACTURER_DATA = 5;
public static final int TYPE_SERVICE_DATA = 6;
- public static final int TYPE_ADVERTISING_DATA_TYPE = 7;
+ public static final int TYPE_ADVERTISING_DATA_TYPE = 8;
// Max length is 31 - 3(flags) - 2 (one byte for length and one byte for type).
private static final int MAX_LEN_PER_FIELD = 26;
diff --git a/android/app/src/com/android/bluetooth/hfp/HeadsetService.java b/android/app/src/com/android/bluetooth/hfp/HeadsetService.java
index 19bffa0..856f7dd 100644
--- a/android/app/src/com/android/bluetooth/hfp/HeadsetService.java
+++ b/android/app/src/com/android/bluetooth/hfp/HeadsetService.java
@@ -27,6 +27,7 @@
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothSinkAudioPolicy;
import android.bluetooth.BluetoothStatusCodes;
import android.bluetooth.BluetoothUuid;
import android.bluetooth.IBluetoothHeadset;
@@ -57,6 +58,7 @@
import com.android.bluetooth.btservice.ProfileService;
import com.android.bluetooth.btservice.ServiceFactory;
import com.android.bluetooth.btservice.storage.DatabaseManager;
+import com.android.bluetooth.hfpclient.HeadsetClientService;
import com.android.bluetooth.le_audio.LeAudioService;
import com.android.bluetooth.telephony.BluetoothInCallService;
import com.android.internal.annotations.VisibleForTesting;
@@ -1364,6 +1366,24 @@
}
/**
+ * Get the Bluetooth Audio Policy stored in the state machine
+ *
+ * @param device the device to change silence mode
+ * @return a {@link BluetoothSinkAudioPolicy} object
+ */
+ public BluetoothSinkAudioPolicy getHfpCallAudioPolicy(BluetoothDevice device) {
+ synchronized (mStateMachines) {
+ final HeadsetStateMachine stateMachine = mStateMachines.get(device);
+ if (stateMachine == null) {
+ Log.e(TAG, "getHfpCallAudioPolicy(), " + device
+ + " does not have a state machine");
+ return null;
+ }
+ return stateMachine.getHfpCallAudioPolicy();
+ }
+ }
+
+ /**
* Remove the active device
*/
private void removeActiveDevice() {
@@ -1891,7 +1911,24 @@
mSystemInterface.getAudioManager().setA2dpSuspended(false);
}
});
-
+ if (callState == HeadsetHalConstants.CALL_STATE_IDLE) {
+ final HeadsetStateMachine stateMachine = mStateMachines.get(mActiveDevice);
+ if (stateMachine == null) {
+ Log.d(TAG, "phoneStateChanged: CALL_STATE_IDLE, mActiveDevice is Null");
+ } else {
+ BluetoothSinkAudioPolicy currentPolicy = stateMachine.getHfpCallAudioPolicy();
+ if (currentPolicy != null && currentPolicy.getActiveDevicePolicyAfterConnection()
+ == BluetoothSinkAudioPolicy.POLICY_NOT_ALLOWED) {
+ /**
+ * If the active device was set because of the pick up audio policy
+ * and the connecting policy is NOT_ALLOWED, then after the call is
+ * terminated, we must de-activate this device.
+ * If there is a fallback mechanism, we should follow it.
+ */
+ removeActiveDevice();
+ }
+ }
+ }
}
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@@ -1940,8 +1977,32 @@
public boolean isInbandRingingEnabled() {
boolean isInbandRingingSupported = getResources().getBoolean(
com.android.bluetooth.R.bool.config_bluetooth_hfp_inband_ringing_support);
+
+ boolean inbandRingtoneAllowedByPolicy = true;
+ List<BluetoothDevice> audioConnectableDevices = getConnectedDevices();
+ if (audioConnectableDevices.size() == 1) {
+ BluetoothDevice connectedDevice = audioConnectableDevices.get(0);
+ BluetoothSinkAudioPolicy callAudioPolicy =
+ getHfpCallAudioPolicy(connectedDevice);
+ if (callAudioPolicy != null && callAudioPolicy.getInBandRingtonePolicy()
+ == BluetoothSinkAudioPolicy.POLICY_NOT_ALLOWED) {
+ inbandRingtoneAllowedByPolicy = false;
+ }
+ }
+
return isInbandRingingSupported && !SystemProperties.getBoolean(
- DISABLE_INBAND_RINGING_PROPERTY, false) && !mInbandRingingRuntimeDisable;
+ DISABLE_INBAND_RINGING_PROPERTY, false)
+ && !mInbandRingingRuntimeDisable
+ && inbandRingtoneAllowedByPolicy
+ && !isHeadsetClientConnected();
+ }
+
+ private boolean isHeadsetClientConnected() {
+ HeadsetClientService headsetClientService = HeadsetClientService.getHeadsetClientService();
+ if (headsetClientService == null) {
+ return false;
+ }
+ return !(headsetClientService.getConnectedDevices().isEmpty());
}
/**
@@ -1956,30 +2017,53 @@
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
public void onConnectionStateChangedFromStateMachine(BluetoothDevice device, int fromState,
int toState) {
- synchronized (mStateMachines) {
- List<BluetoothDevice> audioConnectableDevices =
- getDevicesMatchingConnectionStates(CONNECTING_CONNECTED_STATES);
- if (fromState != BluetoothProfile.STATE_CONNECTED
- && toState == BluetoothProfile.STATE_CONNECTED) {
- if (audioConnectableDevices.size() > 1) {
- mInbandRingingRuntimeDisable = true;
- doForEachConnectedStateMachine(
- stateMachine -> stateMachine.sendMessage(HeadsetStateMachine.SEND_BSIR,
- 0));
- }
- MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.HEADSET);
+ if (fromState != BluetoothProfile.STATE_CONNECTED
+ && toState == BluetoothProfile.STATE_CONNECTED) {
+ updateInbandRinging(device, true);
+ MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.HEADSET);
+ }
+ if (fromState != BluetoothProfile.STATE_DISCONNECTED
+ && toState == BluetoothProfile.STATE_DISCONNECTED) {
+ updateInbandRinging(device, false);
+ if (device.equals(mActiveDevice)) {
+ setActiveDevice(null);
}
- if (fromState != BluetoothProfile.STATE_DISCONNECTED
- && toState == BluetoothProfile.STATE_DISCONNECTED) {
- if (audioConnectableDevices.size() <= 1) {
- mInbandRingingRuntimeDisable = false;
- doForEachConnectedStateMachine(
- stateMachine -> stateMachine.sendMessage(HeadsetStateMachine.SEND_BSIR,
- 1));
- }
- if (device.equals(mActiveDevice)) {
- setActiveDevice(null);
- }
+ }
+ }
+
+ /**
+ * Called from {@link HeadsetClientStateMachine} to update inband ringing status.
+ */
+ public void updateInbandRinging(BluetoothDevice device, boolean connected) {
+ synchronized (mStateMachines) {
+ List<BluetoothDevice> audioConnectableDevices = getConnectedDevices();
+ final int enabled;
+ final boolean inbandRingingRuntimeDisable = mInbandRingingRuntimeDisable;
+
+ if (audioConnectableDevices.size() > 1 || isHeadsetClientConnected()) {
+ mInbandRingingRuntimeDisable = true;
+ enabled = 0;
+ } else {
+ mInbandRingingRuntimeDisable = false;
+ enabled = 1;
+ }
+
+ final boolean updateAll = inbandRingingRuntimeDisable != mInbandRingingRuntimeDisable;
+
+ Log.i(TAG, "updateInbandRinging():"
+ + " Device=" + device
+ + " enabled=" + enabled
+ + " connected=" + connected
+ + " Update all=" + updateAll);
+
+ StateMachineTask sendBsirTask = stateMachine -> stateMachine
+ .sendMessage(HeadsetStateMachine.SEND_BSIR, enabled);
+
+ if (updateAll) {
+ doForEachConnectedStateMachine(sendBsirTask);
+ } else if (connected) {
+ // Same Inband ringing status, send +BSIR only to the new connected device
+ doForStateMachine(device, sendBsirTask);
}
}
}
diff --git a/android/app/src/com/android/bluetooth/hfp/HeadsetStateMachine.java b/android/app/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
index b237e7d..03ea3fa 100644
--- a/android/app/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
+++ b/android/app/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
@@ -25,6 +25,7 @@
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothProtoEnums;
+import android.bluetooth.BluetoothSinkAudioPolicy;
import android.bluetooth.BluetoothStatusCodes;
import android.bluetooth.hfp.BluetoothHfpProtoEnums;
import android.content.Intent;
@@ -43,6 +44,7 @@
import com.android.bluetooth.Utils;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.ProfileService;
+import com.android.bluetooth.btservice.storage.DatabaseManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
@@ -134,6 +136,7 @@
private final AdapterService mAdapterService;
private final HeadsetNativeInterface mNativeInterface;
private final HeadsetSystemInterface mSystemInterface;
+ private DatabaseManager mDatabaseManager;
// Runtime states
@VisibleForTesting
@@ -155,6 +158,10 @@
// Audio disconnect timeout retry count
private int mAudioDisconnectRetry = 0;
+ static final int HFP_SET_AUDIO_POLICY = 1;
+
+ private BluetoothSinkAudioPolicy mHsClientAudioPolicy;
+
// Keys are AT commands, and values are the company IDs.
private static final Map<String, Integer> VENDOR_SPECIFIC_AT_COMMAND_COMPANY_ID;
@@ -187,7 +194,22 @@
mSystemInterface =
Objects.requireNonNull(systemInterface, "systemInterface cannot be null");
mAdapterService = Objects.requireNonNull(adapterService, "AdapterService cannot be null");
+ mDatabaseManager = Objects.requireNonNull(
+ AdapterService.getAdapterService().getDatabase(),
+ "DatabaseManager cannot be null when HeadsetClientStateMachine is created");
mDeviceSilenced = false;
+
+ BluetoothSinkAudioPolicy storedAudioPolicy =
+ mDatabaseManager.getAudioPolicyMetadata(device);
+ if (storedAudioPolicy == null) {
+ Log.w(TAG, "Audio Policy not created in database! Creating...");
+ mHsClientAudioPolicy = new BluetoothSinkAudioPolicy.Builder().build();
+ mDatabaseManager.setAudioPolicyMetadata(device, mHsClientAudioPolicy);
+ } else {
+ Log.i(TAG, "Audio Policy found in database!");
+ mHsClientAudioPolicy = storedAudioPolicy;
+ }
+
// Create phonebook helper
mPhonebook = new AtPhonebook(mHeadsetService, mNativeInterface);
// Initialize state machine
@@ -241,6 +263,8 @@
ProfileService.println(sb, " mMicVolume: " + mMicVolume);
ProfileService.println(sb,
" mConnectingTimestampMs(uptimeMillis): " + mConnectingTimestampMs);
+ ProfileService.println(sb, " mHsClientAudioPolicy: " + mHsClientAudioPolicy.toString());
+
ProfileService.println(sb, " StateMachine: " + this);
// Dump the state machine logs
StringWriter stringWriter = new StringWriter();
@@ -1909,6 +1933,119 @@
}
/**
+ * Process Android specific AT commands.
+ *
+ * @param atString AT command after the "AT+" prefix. Starts with "ANDROID"
+ * @param device Remote device that has sent this command
+ */
+ private void processAndroidAt(String atString, BluetoothDevice device) {
+ log("processAndroidSpecificAt - atString = " + atString);
+
+ if (atString.equals("+ANDROID=?")) {
+ // feature request type command
+ processAndroidAtFeatureRequest(device);
+ } else if (atString.startsWith("+ANDROID=")) {
+ // set type command
+ int equalIndex = atString.indexOf("=");
+ String arg = atString.substring(equalIndex + 1);
+
+ if (arg.isEmpty()) {
+ Log.e(TAG, "Command Invalid!");
+ mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
+ return;
+ }
+
+ Object[] args = generateArgs(arg);
+
+ if (!(args[0] instanceof Integer)) {
+ Log.e(TAG, "Type ID is invalid");
+ mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
+ return;
+ }
+
+ int type = (Integer) args[0];
+
+ if (type == HFP_SET_AUDIO_POLICY) {
+ processAndroidAtSetAudioPolicy(args, device);
+ } else {
+ Log.w(TAG, "Undefined AT+ANDROID command");
+ mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
+ return;
+ }
+ } else {
+ Log.e(TAG, "Undefined AT+ANDROID command");
+ mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_ERROR, 0);
+ return;
+ }
+ mNativeInterface.atResponseCode(device, HeadsetHalConstants.AT_RESPONSE_OK, 0);
+ }
+
+ private void processAndroidAtFeatureRequest(BluetoothDevice device) {
+ /*
+ replying with +ANDROID=1
+ here, 1 is the feature id for audio policy
+
+ currently we only support one type of feature
+ */
+ mNativeInterface.atResponseString(device,
+ BluetoothHeadset.VENDOR_RESULT_CODE_COMMAND_ANDROID
+ + ": " + HFP_SET_AUDIO_POLICY);
+ }
+
+ /**
+ * Process AT+ANDROID AT command
+ *
+ * @param args command arguments after the equal sign
+ * @param device Remote device that has sent this command
+ */
+ private void processAndroidAtSetAudioPolicy(Object[] args, BluetoothDevice device) {
+ if (args.length != 4) {
+ Log.e(TAG, "processAndroidAtSetAudioPolicy() args length must be 4: "
+ + String.valueOf(args.length));
+ return;
+ }
+ if (!(args[1] instanceof Integer) || !(args[2] instanceof Integer)
+ || !(args[3] instanceof Integer)) {
+ Log.e(TAG, "processAndroidAtSetAudioPolicy() argument types not matched");
+ return;
+ }
+
+ if (!mDevice.equals(device)) {
+ Log.e(TAG, "processAndroidAtSetAudioPolicy(): argument device " + device
+ + " doesn't match mDevice " + mDevice);
+ return;
+ }
+
+ int callEstablishPolicy = (Integer) args[1];
+ int connectingTimePolicy = (Integer) args[2];
+ int inbandPolicy = (Integer) args[3];
+
+ setHfpCallAudioPolicy(new BluetoothSinkAudioPolicy.Builder()
+ .setCallEstablishPolicy(callEstablishPolicy)
+ .setActiveDevicePolicyAfterConnection(connectingTimePolicy)
+ .setInBandRingtonePolicy(inbandPolicy)
+ .build());
+ }
+
+ /**
+ * sets the audio policy of the client device and stores in the database
+ *
+ * @param policies policies to be set and stored
+ */
+ public void setHfpCallAudioPolicy(BluetoothSinkAudioPolicy policies) {
+ mHsClientAudioPolicy = policies;
+ mDatabaseManager.setAudioPolicyMetadata(mDevice, policies);
+ }
+
+ /**
+ * get the audio policy of the client device
+ *
+ */
+ public BluetoothSinkAudioPolicy getHfpCallAudioPolicy() {
+ return mHsClientAudioPolicy;
+ }
+
+ /**
* Process AT+XAPL AT command
*
* @param args command arguments after the equal sign
@@ -1959,6 +2096,8 @@
processAtCpbs(atCommand.substring(5), commandType, device);
} else if (atCommand.startsWith("+CPBR")) {
processAtCpbr(atCommand.substring(5), commandType, device);
+ } else if (atCommand.startsWith("+ANDROID")) {
+ processAndroidAt(atCommand, device);
} else {
processVendorSpecificAt(atCommand, device);
}
diff --git a/android/app/src/com/android/bluetooth/hfp/HeadsetSystemInterface.java b/android/app/src/com/android/bluetooth/hfp/HeadsetSystemInterface.java
index 08f4bdb..bbf4e36 100644
--- a/android/app/src/com/android/bluetooth/hfp/HeadsetSystemInterface.java
+++ b/android/app/src/com/android/bluetooth/hfp/HeadsetSystemInterface.java
@@ -21,6 +21,7 @@
import android.annotation.RequiresPermission;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothSinkAudioPolicy;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Intent;
@@ -155,13 +156,19 @@
@VisibleForTesting
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
public void answerCall(BluetoothDevice device) {
+ Log.d(TAG, "answerCall");
if (device == null) {
Log.w(TAG, "answerCall device is null");
return;
}
BluetoothInCallService bluetoothInCallService = getBluetoothInCallServiceInstance();
if (bluetoothInCallService != null) {
- mHeadsetService.setActiveDevice(device);
+ BluetoothSinkAudioPolicy callAudioPolicy =
+ mHeadsetService.getHfpCallAudioPolicy(device);
+ if (callAudioPolicy == null || callAudioPolicy.getCallEstablishPolicy()
+ != BluetoothSinkAudioPolicy.POLICY_NOT_ALLOWED) {
+ mHeadsetService.setActiveDevice(device);
+ }
bluetoothInCallService.answerCall();
} else {
Log.e(TAG, "Handsfree phone proxy null for answering call");
diff --git a/android/app/src/com/android/bluetooth/hfpclient/HeadsetClientService.java b/android/app/src/com/android/bluetooth/hfpclient/HeadsetClientService.java
index db43df5..9221d98 100644
--- a/android/app/src/com/android/bluetooth/hfpclient/HeadsetClientService.java
+++ b/android/app/src/com/android/bluetooth/hfpclient/HeadsetClientService.java
@@ -21,6 +21,8 @@
import android.bluetooth.BluetoothHeadsetClient;
import android.bluetooth.BluetoothHeadsetClientCall;
import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothSinkAudioPolicy;
+import android.bluetooth.BluetoothStatusCodes;
import android.bluetooth.IBluetoothHeadsetClient;
import android.content.AttributionSource;
import android.content.BroadcastReceiver;
@@ -32,6 +34,7 @@
import android.os.Bundle;
import android.os.HandlerThread;
import android.os.Message;
+import android.os.SystemProperties;
import android.sysprop.BluetoothProperties;
import android.util.Log;
@@ -59,7 +62,7 @@
* @hide
*/
public class HeadsetClientService extends ProfileService {
- private static final boolean DBG = false;
+ private static final boolean DBG = true;
private static final String TAG = "HeadsetClientService";
// This is also used as a lock for shared data in {@link HeadsetClientService}
@@ -612,8 +615,10 @@
List<BluetoothHeadsetClientCall> defaultValue = new ArrayList<>();
if (service != null) {
List<HfpClientCall> calls = service.getCurrentCalls(device);
- for (HfpClientCall call : calls) {
- defaultValue.add(toLegacyCall(call));
+ if (calls != null) {
+ for (HfpClientCall call : calls) {
+ defaultValue.add(toLegacyCall(call));
+ }
}
}
receiver.send(defaultValue);
@@ -923,6 +928,53 @@
return false;
}
+ /**
+ * sends the {@link BluetoothSinkAudioPolicy} object to the state machine of the corresponding
+ * device to store and send to the remote device using Android specific AT commands.
+ *
+ * @param device for whom the policies to be set
+ * @param policies to be set policies
+ */
+ public void setAudioPolicy(BluetoothDevice device, BluetoothSinkAudioPolicy policies) {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ Log.i(TAG, "setAudioPolicy: device=" + device + ", " + policies.toString() + ", "
+ + Utils.getUidPidString());
+ HeadsetClientStateMachine sm = getStateMachine(device);
+ if (sm != null) {
+ sm.setAudioPolicy(policies);
+ }
+ }
+
+ /**
+ * sets the audio policy feature support status for the corresponding device.
+ *
+ * @param device for whom the policies to be set
+ * @param supported support status
+ */
+ public void setAudioPolicyRemoteSupported(BluetoothDevice device, boolean supported) {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ Log.i(TAG, "setAudioPolicyRemoteSupported: " + supported);
+ HeadsetClientStateMachine sm = getStateMachine(device);
+ if (sm != null) {
+ sm.setAudioPolicyRemoteSupported(supported);
+ }
+ }
+
+ /**
+ * gets the audio policy feature support status for the corresponding device.
+ *
+ * @param device for whom the policies to be set
+ * @return int support status
+ */
+ public int getAudioPolicyRemoteSupported(BluetoothDevice device) {
+ enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
+ HeadsetClientStateMachine sm = getStateMachine(device);
+ if (sm != null) {
+ return sm.getAudioPolicyRemoteSupported();
+ }
+ return BluetoothStatusCodes.FEATURE_NOT_CONFIGURED;
+ }
+
public boolean connectAudio(BluetoothDevice device) {
Log.i(TAG, "connectAudio: device=" + device + ", " + Utils.getUidPidString());
HeadsetClientStateMachine sm = getStateMachine(device);
@@ -1078,6 +1130,14 @@
return null;
}
+ // Some platform does not support three way calling (ex: watch)
+ final boolean support_three_way_calling = SystemProperties
+ .getBoolean("bluetooth.headset_client.three_way_calling.enabled", true);
+ if (!support_three_way_calling && !getCurrentCalls(device).isEmpty()) {
+ Log.e(TAG, String.format("dial(%s): Line is busy, reject dialing", device));
+ return null;
+ }
+
HfpClientCall call = new HfpClientCall(device,
HeadsetClientStateMachine.HF_ORIGINATED_CALL_ID,
HfpClientCall.CALL_STATE_DIALING, number, false /* multiparty */,
diff --git a/android/app/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java b/android/app/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java
index 557d74b..804dadd 100644
--- a/android/app/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java
+++ b/android/app/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachine.java
@@ -41,6 +41,8 @@
import android.bluetooth.BluetoothHeadsetClient;
import android.bluetooth.BluetoothHeadsetClient.NetworkServiceState;
import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothSinkAudioPolicy;
+import android.bluetooth.BluetoothStatusCodes;
import android.bluetooth.BluetoothUuid;
import android.bluetooth.hfp.BluetoothHfpProtoEnums;
import android.content.Intent;
@@ -52,6 +54,7 @@
import android.os.Message;
import android.os.ParcelUuid;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.util.Log;
import android.util.Pair;
@@ -62,6 +65,7 @@
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.MetricsLogger;
import com.android.bluetooth.btservice.ProfileService;
+import com.android.bluetooth.hfp.HeadsetService;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IState;
import com.android.internal.util.State;
@@ -108,6 +112,7 @@
public static final int DISABLE_NREC = 20;
public static final int SEND_VENDOR_AT_COMMAND = 21;
public static final int SEND_BIEV = 22;
+ public static final int SEND_ANDROID_AT_COMMAND = 23;
// internal actions
@VisibleForTesting
@@ -145,6 +150,7 @@
private long mClccTimer = 0;
private final HeadsetClientService mService;
+ private final HeadsetService mHeadsetService;
// Set of calls that represent the accurate state of calls that exists on AG and the calls that
// are currently in process of being notified to the AG from HF.
@@ -179,6 +185,12 @@
int mAudioState;
// Indicates whether audio can be routed to the device
private boolean mAudioRouteAllowed;
+
+ private static final int CALL_AUDIO_POLICY_FEATURE_ID = 1;
+
+ public int mAudioPolicyRemoteSupported;
+ private BluetoothSinkAudioPolicy mHsClientAudioPolicy;
+
private boolean mAudioWbs;
private int mVoiceRecognitionActive;
private final BluetoothAdapter mAdapter;
@@ -226,6 +238,8 @@
ProfileService.println(sb, " mOperatorName: " + mOperatorName);
ProfileService.println(sb, " mSubscriberInfo: " + mSubscriberInfo);
ProfileService.println(sb, " mAudioRouteAllowed: " + mAudioRouteAllowed);
+ ProfileService.println(sb, " mAudioPolicyRemoteSupported: " + mAudioPolicyRemoteSupported);
+ ProfileService.println(sb, " mHsClientAudioPolicy: " + mHsClientAudioPolicy);
ProfileService.println(sb, " mCalls:");
if (mCalls != null) {
@@ -529,7 +543,12 @@
}
if (mCalls.size() > 0) {
- if (mService.getResources().getBoolean(R.bool.hfp_clcc_poll_during_call)) {
+ // Continue polling even if not enabled until the new outgoing call is associated with
+ // a valid call on the phone. The polling would at most continue until
+ // OUTGOING_TIMEOUT_MILLI. This handles the potential scenario where the phone creates
+ // and terminates a call before the first QUERY_CURRENT_CALLS completes.
+ if (mService.getResources().getBoolean(R.bool.hfp_clcc_poll_during_call)
+ || (mCalls.containsKey(HF_ORIGINATED_CALL_ID))) {
sendMessageDelayed(QUERY_CURRENT_CALLS,
mService.getResources().getInteger(
R.integer.hfp_clcc_poll_interval_during_call));
@@ -844,12 +863,13 @@
return (bitfield & mask) == mask;
}
- HeadsetClientStateMachine(HeadsetClientService context, Looper looper,
- NativeInterface nativeInterface) {
+ HeadsetClientStateMachine(HeadsetClientService context, HeadsetService headsetService,
+ Looper looper, NativeInterface nativeInterface) {
super(TAG, looper);
mService = context;
mNativeInterface = nativeInterface;
mAudioManager = mService.getAudioManager();
+ mHeadsetService = headsetService;
mVendorProcessor = new VendorCommandResponseProcessor(mService, mNativeInterface);
@@ -861,6 +881,8 @@
mAudioRouteAllowed = context.getResources().getBoolean(
R.bool.headset_client_initial_audio_route_allowed);
+ mHsClientAudioPolicy = new BluetoothSinkAudioPolicy.Builder().build();
+
mIndicatorNetworkState = HeadsetClientHalConstants.NETWORK_STATE_NOT_AVAILABLE;
mIndicatorNetworkType = HeadsetClientHalConstants.SERVICE_TYPE_HOME;
mIndicatorNetworkSignal = 0;
@@ -891,11 +913,12 @@
setInitialState(mDisconnected);
}
- static HeadsetClientStateMachine make(HeadsetClientService context, Looper looper,
- NativeInterface nativeInterface) {
+ static HeadsetClientStateMachine make(HeadsetClientService context,
+ HeadsetService headsetService,
+ Looper looper, NativeInterface nativeInterface) {
logD("make");
- HeadsetClientStateMachine hfcsm = new HeadsetClientStateMachine(context, looper,
- nativeInterface);
+ HeadsetClientStateMachine hfcsm = new HeadsetClientStateMachine(context, headsetService,
+ looper, nativeInterface);
hfcsm.start();
return hfcsm;
}
@@ -1004,6 +1027,9 @@
Log.e(TAG, "Disconnected: Illegal state transition from " + mPrevState.getName()
+ " to Disconnected, mCurrentDevice=" + mCurrentDevice);
}
+ if (mHeadsetService != null && mCurrentDevice != null) {
+ mHeadsetService.updateInbandRinging(mCurrentDevice, false);
+ }
mCurrentDevice = null;
}
@@ -1143,6 +1169,40 @@
deferMessage(message);
break;
case StackEvent.EVENT_TYPE_CMD_RESULT:
+ logD("Connecting: CMD_RESULT valueInt:" + event.valueInt
+ + " mQueuedActions.size=" + mQueuedActions.size());
+ if (!mQueuedActions.isEmpty()) {
+ logD("queuedAction:" + mQueuedActions.peek().first);
+ }
+ Pair<Integer, Object> queuedAction = mQueuedActions.poll();
+ if (queuedAction == null || queuedAction.first == NO_ACTION) {
+ break;
+ }
+ switch (queuedAction.first) {
+ case SEND_ANDROID_AT_COMMAND:
+ if (event.valueInt == StackEvent.CMD_RESULT_TYPE_OK) {
+ Log.w(TAG, "Received OK instead of +ANDROID");
+ } else {
+ Log.w(TAG, "Received ERROR instead of +ANDROID");
+ }
+ setAudioPolicyRemoteSupported(false);
+ transitionTo(mConnected);
+ break;
+ default:
+ Log.w(TAG, "Ignored CMD Result");
+ break;
+ }
+ break;
+
+ case StackEvent.EVENT_TYPE_UNKNOWN_EVENT:
+ if (mVendorProcessor.processEvent(event.valueString, event.device)) {
+ mQueuedActions.poll();
+ transitionTo(mConnected);
+ } else {
+ Log.e(TAG, "Unknown event :" + event.valueString
+ + " for device " + event.device);
+ }
+ break;
case StackEvent.EVENT_TYPE_SUBSCRIBER_INFO:
case StackEvent.EVENT_TYPE_CURRENT_CALLS:
case StackEvent.EVENT_TYPE_OPERATOR_NAME:
@@ -1206,7 +1266,11 @@
mAudioManager.isMicrophoneMute() ? 0 : 15, 0));
// query subscriber info
deferMessage(obtainMessage(HeadsetClientStateMachine.SUBSCRIBER_INFO));
- transitionTo(mConnected);
+
+ if (!queryRemoteSupportedFeatures()) {
+ Log.w(TAG, "Couldn't query Android AT remote supported!");
+ transitionTo(mConnected);
+ }
break;
case HeadsetClientHalConstants.CONNECTION_STATE_CONNECTED:
@@ -1253,6 +1317,9 @@
if (mPrevState == mConnecting) {
broadcastConnectionState(mCurrentDevice, BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.STATE_CONNECTING);
+ if (mHeadsetService != null) {
+ mHeadsetService.updateInbandRinging(mCurrentDevice, true);
+ }
MetricsLogger.logProfileConnectionEvent(
BluetoothMetricsProto.ProfileId.HEADSET_CLIENT);
} else if (mPrevState != mAudioOn) {
@@ -1547,8 +1614,11 @@
if (event.valueInt == HeadsetClientHalConstants.VOLUME_TYPE_SPK) {
mCommandedSpeakerVolume = hfToAmVol(event.valueInt2);
logD("AM volume set to " + mCommandedSpeakerVolume);
+ boolean show_volume = SystemProperties
+ .getBoolean("bluetooth.hfp_volume_control.enabled", true);
mAudioManager.setStreamVolume(AudioManager.STREAM_VOICE_CALL,
- +mCommandedSpeakerVolume, AudioManager.FLAG_SHOW_UI);
+ +mCommandedSpeakerVolume,
+ show_volume ? AudioManager.FLAG_SHOW_UI : 0);
} else if (event.valueInt
== HeadsetClientHalConstants.VOLUME_TYPE_MIC) {
mAudioManager.setMicrophoneMute(event.valueInt2 == 0);
@@ -1588,6 +1658,8 @@
oldState, mVoiceRecognitionActive);
}
break;
+ case SEND_ANDROID_AT_COMMAND:
+ logD("Connected: Received OK for AT+ANDROID");
default:
Log.w(TAG, "Unhandled AT OK " + event);
break;
@@ -2089,9 +2161,81 @@
public void setAudioRouteAllowed(boolean allowed) {
mAudioRouteAllowed = allowed;
+
+ int establishPolicy = allowed
+ ? BluetoothSinkAudioPolicy.POLICY_ALLOWED :
+ BluetoothSinkAudioPolicy.POLICY_NOT_ALLOWED;
+
+ /*
+ * Backward compatibility for mAudioRouteAllowed
+ */
+ setAudioPolicy(new BluetoothSinkAudioPolicy.Builder(mHsClientAudioPolicy)
+ .setCallEstablishPolicy(establishPolicy).build());
}
public boolean getAudioRouteAllowed() {
return mAudioRouteAllowed;
}
+
+ private String createMaskString(BluetoothSinkAudioPolicy policies) {
+ StringBuilder mask = new StringBuilder();
+ mask.append(Integer.toString(CALL_AUDIO_POLICY_FEATURE_ID));
+ mask.append("," + policies.getCallEstablishPolicy());
+ mask.append("," + policies.getActiveDevicePolicyAfterConnection());
+ mask.append("," + policies.getInBandRingtonePolicy());
+ return mask.toString();
+ }
+
+ /**
+ * sets the {@link BluetoothSinkAudioPolicy} object device and send to the remote
+ * device using Android specific AT commands.
+ *
+ * @param policies to be set policies
+ */
+ public void setAudioPolicy(BluetoothSinkAudioPolicy policies) {
+ logD("setAudioPolicy: " + policies);
+ mHsClientAudioPolicy = policies;
+
+ if (mAudioPolicyRemoteSupported != BluetoothStatusCodes.FEATURE_SUPPORTED) {
+ Log.e(TAG, "Audio Policy feature not supported!");
+ return;
+ }
+
+ if (!mNativeInterface.sendAndroidAt(mCurrentDevice,
+ "+ANDROID=" + createMaskString(policies))) {
+ Log.e(TAG, "ERROR: Couldn't send call audio policies");
+ }
+ }
+
+ private boolean queryRemoteSupportedFeatures() {
+ Log.i(TAG, "queryRemoteSupportedFeatures");
+ if (!mNativeInterface.sendAndroidAt(mCurrentDevice, "+ANDROID=?")) {
+ Log.e(TAG, "ERROR: Couldn't send audio policy feature query");
+ return false;
+ }
+ addQueuedAction(SEND_ANDROID_AT_COMMAND);
+ return true;
+ }
+
+ /**
+ * sets the audio policy feature support status
+ *
+ * @param supported support status
+ */
+ public void setAudioPolicyRemoteSupported(boolean supported) {
+ if (supported) {
+ mAudioPolicyRemoteSupported = BluetoothStatusCodes.FEATURE_SUPPORTED;
+ } else {
+ mAudioPolicyRemoteSupported = BluetoothStatusCodes.FEATURE_NOT_SUPPORTED;
+ }
+ }
+
+ /**
+ * gets the audio policy feature support status
+ *
+ * @return int support status
+ */
+ public int getAudioPolicyRemoteSupported() {
+ return mAudioPolicyRemoteSupported;
+ }
}
diff --git a/android/app/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineFactory.java b/android/app/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineFactory.java
index b0c7265..b21f537 100644
--- a/android/app/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineFactory.java
+++ b/android/app/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineFactory.java
@@ -18,6 +18,8 @@
import android.os.HandlerThread;
+import com.android.bluetooth.hfp.HeadsetService;
+
// Factory so that StateMachine objected can be mocked
public class HeadsetClientStateMachineFactory {
/**
@@ -26,6 +28,7 @@
*/
public HeadsetClientStateMachine make(HeadsetClientService context, HandlerThread t,
NativeInterface nativeInterface) {
- return HeadsetClientStateMachine.make(context, t.getLooper(), nativeInterface);
+ return HeadsetClientStateMachine.make(context, HeadsetService.getHeadsetService(),
+ t.getLooper(), nativeInterface);
}
}
diff --git a/android/app/src/com/android/bluetooth/hfpclient/NativeInterface.java b/android/app/src/com/android/bluetooth/hfpclient/NativeInterface.java
index 1cb2cf7..2615b73 100644
--- a/android/app/src/com/android/bluetooth/hfpclient/NativeInterface.java
+++ b/android/app/src/com/android/bluetooth/hfpclient/NativeInterface.java
@@ -266,6 +266,17 @@
return sendATCmdNative(getByteAddress(device), atCmd, val1, val2, arg);
}
+ /**
+ * Set call audio policy to the specified paired device
+ *
+ * @param cmd Android specific command string
+ * @return True on success, False on failure
+ */
+ @VisibleForTesting
+ public boolean sendAndroidAt(BluetoothDevice device, String cmd) {
+ return sendAndroidAtNative(getByteAddress(device), cmd);
+ }
+
// Native methods that call into the JNI interface
private static native void classInitNative();
@@ -306,6 +317,8 @@
private static native boolean sendATCmdNative(byte[] address, int atCmd, int val1, int val2,
String arg);
+ private static native boolean sendAndroidAtNative(byte[] address, String cmd);
+
private BluetoothDevice getDevice(byte[] address) {
return mAdapterService.getDeviceFromByte(address);
}
diff --git a/android/app/src/com/android/bluetooth/hfpclient/StackEvent.java b/android/app/src/com/android/bluetooth/hfpclient/StackEvent.java
index a0b8ce6..4c08946 100644
--- a/android/app/src/com/android/bluetooth/hfpclient/StackEvent.java
+++ b/android/app/src/com/android/bluetooth/hfpclient/StackEvent.java
@@ -51,6 +51,9 @@
public static final int EVENT_TYPE_RING_INDICATION = 21;
public static final int EVENT_TYPE_UNKNOWN_EVENT = 22;
+ public static final int CMD_RESULT_TYPE_OK = 0;
+ public static final int CMD_RESULT_TYPE_CME_ERROR = 7;
+
public int type = EVENT_TYPE_NONE;
public int valueInt = 0;
public int valueInt2 = 0;
diff --git a/android/app/src/com/android/bluetooth/hfpclient/VendorCommandResponseProcessor.java b/android/app/src/com/android/bluetooth/hfpclient/VendorCommandResponseProcessor.java
index 205ba40..cd2ec0d 100644
--- a/android/app/src/com/android/bluetooth/hfpclient/VendorCommandResponseProcessor.java
+++ b/android/app/src/com/android/bluetooth/hfpclient/VendorCommandResponseProcessor.java
@@ -69,6 +69,9 @@
SUPPORTED_VENDOR_EVENTS.put(
"+XAPL=",
BluetoothAssignedNumbers.APPLE);
+ SUPPORTED_VENDOR_EVENTS.put(
+ "+ANDROID:",
+ BluetoothAssignedNumbers.GOOGLE);
}
VendorCommandResponseProcessor(HeadsetClientService context, NativeInterface nativeInterface) {
@@ -148,10 +151,14 @@
if (vendorId == null) {
Log.e(TAG, "Invalid response: " + atString + ". " + eventCode);
return false;
+ } else if (vendorId == BluetoothAssignedNumbers.GOOGLE) {
+ Log.i(TAG, "received +ANDROID event. Setting Audio policy to true");
+ mService.setAudioPolicyRemoteSupported(device, true);
+ } else {
+ broadcastVendorSpecificEventIntent(vendorId, eventCode, atString, device);
+ logD("process vendor event " + vendorId + ", " + eventCode + ", "
+ + atString + " for device" + device);
}
- broadcastVendorSpecificEventIntent(vendorId, eventCode, atString, device);
- logD("process vendor event " + vendorId + ", " + eventCode + ", "
- + atString + " for device" + device);
return true;
}
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 42243ab..9921c33 100644
--- a/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java
+++ b/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java
@@ -77,7 +77,6 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
-import java.util.concurrent.ConcurrentHashMap;
/**
* Provides Bluetooth LeAudio profile, as a service in the Bluetooth application.
@@ -91,7 +90,7 @@
private static final int SM_THREAD_JOIN_TIMEOUT_MS = 1000;
// Upper limit of all LeAudio devices: Bonded or Connected
- private static final int MAX_LE_AUDIO_STATE_MACHINES = 10;
+ private static final int MAX_LE_AUDIO_DEVICES = 10;
private static LeAudioService sLeAudioService;
/**
@@ -156,16 +155,27 @@
BluetoothDevice mLostLeadDeviceWhileStreaming;
}
+ private static class LeAudioDeviceDescriptor {
+ LeAudioDeviceDescriptor() {
+ mStateMachine = null;
+ mGroupId = LE_AUDIO_GROUP_ID_INVALID;
+ mSinkAudioLocation = BluetoothLeAudio.AUDIO_LOCATION_INVALID;
+ mDirection = AUDIO_DIRECTION_NONE;
+ }
+
+ public LeAudioStateMachine mStateMachine;
+ public Integer mGroupId;
+ public Integer mSinkAudioLocation;
+ public Integer mDirection;
+ }
+
List<BluetoothLeAudioCodecConfig> mInputLocalCodecCapabilities = new ArrayList<>();
List<BluetoothLeAudioCodecConfig> mOutputLocalCodecCapabilities = new ArrayList<>();
@GuardedBy("mGroupLock")
private final Map<Integer, LeAudioGroupDescriptor> mGroupDescriptors = new LinkedHashMap<>();
- private final Map<BluetoothDevice, LeAudioStateMachine> mStateMachines = new LinkedHashMap<>();
-
- @GuardedBy("mGroupLock")
- private final Map<BluetoothDevice, Integer> mDeviceGroupIdMap = new ConcurrentHashMap<>();
- private final Map<BluetoothDevice, Integer> mDeviceAudioLocationMap = new ConcurrentHashMap<>();
+ private final Map<BluetoothDevice, LeAudioDeviceDescriptor> mDeviceDescriptors =
+ new LinkedHashMap<>();
private BroadcastReceiver mBondStateChangedReceiver;
private BroadcastReceiver mConnectionStateChangedReceiver;
@@ -219,17 +229,15 @@
"AudioManager cannot be null when LeAudioService starts");
// Start handler thread for state machines
- mStateMachines.clear();
mStateMachinesThread = new HandlerThread("LeAudioService.StateMachines");
mStateMachinesThread.start();
- mDeviceAudioLocationMap.clear();
mBroadcastStateMap.clear();
mBroadcastMetadataList.clear();
mBroadcastsPlaybackMap.clear();
synchronized (mGroupLock) {
- mDeviceGroupIdMap.clear();
+ mDeviceDescriptors.clear();
mGroupDescriptors.clear();
}
@@ -326,7 +334,18 @@
break;
}
}
- mDeviceGroupIdMap.clear();
+
+ // Destroy state machines and stop handler thread
+ for (LeAudioDeviceDescriptor descriptor : mDeviceDescriptors.values()) {
+ LeAudioStateMachine sm = descriptor.mStateMachine;
+ if (sm == null) {
+ continue;
+ }
+ sm.doQuit();
+ sm.cleanup();
+ }
+
+ mDeviceDescriptors.clear();
mGroupDescriptors.clear();
}
@@ -339,7 +358,6 @@
mActiveAudioOutDevice = null;
mActiveAudioInDevice = null;
- mDatabaseManager = null;
mLeAudioCodecConfig = null;
// Set the service and BLE devices as inactive
@@ -353,16 +371,6 @@
unregisterReceiver(mMuteStateChangedReceiver);
mMuteStateChangedReceiver = null;
- // Destroy state machines and stop handler thread
- synchronized (mStateMachines) {
- for (LeAudioStateMachine sm : mStateMachines.values()) {
- sm.doQuit();
- sm.cleanup();
- }
- mStateMachines.clear();
- }
-
- mDeviceAudioLocationMap.clear();
if (mBroadcastCallbacks != null) {
mBroadcastCallbacks.kill();
@@ -440,6 +448,27 @@
return mVolumeControlService.getAudioDeviceGroupVolume(groupId);
}
+ LeAudioDeviceDescriptor createDeviceDescriptor(BluetoothDevice device) {
+ LeAudioDeviceDescriptor descriptor = mDeviceDescriptors.get(device);
+ if (descriptor == null) {
+
+ // Limit the maximum number of devices to avoid DoS attack
+ if (mDeviceDescriptors.size() >= MAX_LE_AUDIO_DEVICES) {
+ Log.e(TAG, "Maximum number of LeAudio state machines reached: "
+ + MAX_LE_AUDIO_DEVICES);
+ return null;
+ }
+
+ mDeviceDescriptors.put(device, new LeAudioDeviceDescriptor());
+ descriptor = mDeviceDescriptors.get(device);
+ Log.d(TAG, "Created descriptor for device: " + device);
+ } else {
+ Log.w(TAG, "Device: " + device + ", already exists");
+ }
+
+ return descriptor;
+ }
+
public boolean connect(BluetoothDevice device) {
if (DBG) {
Log.d(TAG, "connect(): " + device);
@@ -455,7 +484,11 @@
return false;
}
- synchronized (mStateMachines) {
+ synchronized (mGroupLock) {
+ if (createDeviceDescriptor(device) == null) {
+ return false;
+ }
+
LeAudioStateMachine sm = getOrCreateStateMachine(device);
if (sm == null) {
Log.e(TAG, "Ignored connect request for " + device + " : no state machine");
@@ -478,9 +511,14 @@
Log.d(TAG, "disconnect(): " + device);
}
- // Disconnect this device
- synchronized (mStateMachines) {
- LeAudioStateMachine sm = mStateMachines.get(device);
+ synchronized (mGroupLock) {
+ LeAudioDeviceDescriptor descriptor = getDeviceDescriptor(device);
+ if (descriptor == null) {
+ Log.e(TAG, "disconnect: No valid descriptor for device: " + device);
+ return false;
+ }
+
+ LeAudioStateMachine sm = descriptor.mStateMachine;
if (sm == null) {
Log.e(TAG, "Ignored disconnect request for " + device
+ " : no state machine");
@@ -493,10 +531,11 @@
}
public List<BluetoothDevice> getConnectedDevices() {
- synchronized (mStateMachines) {
+ synchronized (mGroupLock) {
List<BluetoothDevice> devices = new ArrayList<>();
- for (LeAudioStateMachine sm : mStateMachines.values()) {
- if (sm.isConnected()) {
+ for (LeAudioDeviceDescriptor descriptor : mDeviceDescriptors.values()) {
+ LeAudioStateMachine sm = descriptor.mStateMachine;
+ if (sm != null && sm.isConnected()) {
devices.add(sm.getDevice());
}
}
@@ -505,11 +544,31 @@
}
BluetoothDevice getConnectedGroupLeadDevice(int groupId) {
+ BluetoothDevice device = null;
+
if (mActiveAudioOutDevice != null
&& getGroupId(mActiveAudioOutDevice) == groupId) {
- return mActiveAudioOutDevice;
+ device = mActiveAudioOutDevice;
+ } else {
+ device = getFirstDeviceFromGroup(groupId);
}
- return getFirstDeviceFromGroup(groupId);
+
+ if (device == null) {
+ return device;
+ }
+
+ LeAudioDeviceDescriptor descriptor = getDeviceDescriptor(device);
+ if (descriptor == null) {
+ Log.e(TAG, "getConnectedGroupLeadDevice: No valid descriptor for device: " + device);
+ return null;
+ }
+
+ LeAudioStateMachine sm = descriptor.mStateMachine;
+ if (sm != null && sm.getConnectionState() == BluetoothProfile.STATE_CONNECTED) {
+ return device;
+ }
+
+ return null;
}
List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
@@ -521,14 +580,21 @@
if (bondedDevices == null) {
return devices;
}
- synchronized (mStateMachines) {
+ synchronized (mGroupLock) {
for (BluetoothDevice device : bondedDevices) {
final ParcelUuid[] featureUuids = device.getUuids();
if (!Utils.arrayContains(featureUuids, BluetoothUuid.LE_AUDIO)) {
continue;
}
int connectionState = BluetoothProfile.STATE_DISCONNECTED;
- LeAudioStateMachine sm = mStateMachines.get(device);
+ LeAudioDeviceDescriptor descriptor = getDeviceDescriptor(device);
+ if (descriptor == null) {
+ Log.e(TAG, "getDevicesMatchingConnectionStates: "
+ + "No valid descriptor for device: " + device);
+ return null;
+ }
+
+ LeAudioStateMachine sm = descriptor.mStateMachine;
if (sm != null) {
connectionState = sm.getConnectionState();
}
@@ -551,9 +617,11 @@
@VisibleForTesting
List<BluetoothDevice> getDevices() {
List<BluetoothDevice> devices = new ArrayList<>();
- synchronized (mStateMachines) {
- for (LeAudioStateMachine sm : mStateMachines.values()) {
- devices.add(sm.getDevice());
+ synchronized (mGroupLock) {
+ for (LeAudioDeviceDescriptor descriptor : mDeviceDescriptors.values()) {
+ if (descriptor.mStateMachine != null) {
+ devices.add(descriptor.mStateMachine.getDevice());
+ }
}
return devices;
}
@@ -569,8 +637,13 @@
* {@link BluetoothProfile#STATE_DISCONNECTING} if this profile is being disconnected
*/
public int getConnectionState(BluetoothDevice device) {
- synchronized (mStateMachines) {
- LeAudioStateMachine sm = mStateMachines.get(device);
+ synchronized (mGroupLock) {
+ LeAudioDeviceDescriptor descriptor = getDeviceDescriptor(device);
+ if (descriptor == null) {
+ return BluetoothProfile.STATE_DISCONNECTED;
+ }
+
+ LeAudioStateMachine sm = descriptor.mStateMachine;
if (sm == null) {
return BluetoothProfile.STATE_DISCONNECTED;
}
@@ -611,8 +684,10 @@
* @param group_id group Id to verify
* @return true given group exists, otherwise false
*/
- public boolean isValidDeviceGroup(int group_id) {
- return group_id != LE_AUDIO_GROUP_ID_INVALID && mDeviceGroupIdMap.containsValue(group_id);
+ public boolean isValidDeviceGroup(int groupId) {
+ synchronized (mGroupLock) {
+ return groupId != LE_AUDIO_GROUP_ID_INVALID && mGroupDescriptors.containsKey(groupId);
+ }
}
/**
@@ -628,8 +703,9 @@
}
synchronized (mGroupLock) {
- for (Map.Entry<BluetoothDevice, Integer> entry : mDeviceGroupIdMap.entrySet()) {
- if (entry.getValue() == groupId) {
+ for (Map.Entry<BluetoothDevice, LeAudioDeviceDescriptor> entry
+ : mDeviceDescriptors.entrySet()) {
+ if (entry.getValue().mGroupId == groupId) {
result.add(entry.getKey());
}
}
@@ -773,15 +849,16 @@
return null;
}
synchronized (mGroupLock) {
- for (Map.Entry<BluetoothDevice, Integer> entry : mDeviceGroupIdMap.entrySet()) {
- if (entry.getValue() != groupId) {
+ for (LeAudioDeviceDescriptor descriptor : mDeviceDescriptors.values()) {
+ if (!descriptor.mGroupId.equals(groupId)) {
continue;
}
- LeAudioStateMachine sm = mStateMachines.get(entry.getKey());
+
+ LeAudioStateMachine sm = descriptor.mStateMachine;
if (sm == null || sm.getConnectionState() != BluetoothProfile.STATE_CONNECTED) {
continue;
}
- return entry.getKey();
+ return sm.getDevice();
}
}
return null;
@@ -803,8 +880,13 @@
}
if (device != null && mActiveAudioInDevice != null) {
- int previousGroupId = getGroupId(mActiveAudioInDevice);
- if (previousGroupId == groupId) {
+ LeAudioDeviceDescriptor deviceDescriptor = getDeviceDescriptor(device);
+ if (deviceDescriptor == null) {
+ Log.e(TAG, "updateActiveInDevice: No valid descriptor for device: " + device);
+ return false;
+ }
+
+ if (deviceDescriptor.mGroupId.equals(groupId)) {
/* This is thes same group as aleady notified to the system.
* Therefore do not change the device we have connected to the group,
* unless, previous one is disconnected now
@@ -812,9 +894,9 @@
if (mActiveAudioInDevice.isConnected()) {
device = mActiveAudioInDevice;
}
- } else if (previousGroupId != LE_AUDIO_GROUP_ID_INVALID) {
+ } else if (deviceDescriptor.mGroupId != LE_AUDIO_GROUP_ID_INVALID) {
/* Mark old group as no active */
- LeAudioGroupDescriptor descriptor = getGroupDescriptor(previousGroupId);
+ LeAudioGroupDescriptor descriptor = getGroupDescriptor(deviceDescriptor.mGroupId);
if (descriptor != null) {
descriptor.mIsActive = false;
}
@@ -860,8 +942,13 @@
}
if (device != null && mActiveAudioOutDevice != null) {
- int previousGroupId = getGroupId(mActiveAudioOutDevice);
- if (previousGroupId == groupId) {
+ LeAudioDeviceDescriptor deviceDescriptor = getDeviceDescriptor(device);
+ if (deviceDescriptor == null) {
+ Log.e(TAG, "updateActiveOutDevice: No valid descriptor for device: " + device);
+ return false;
+ }
+
+ if (deviceDescriptor.mGroupId.equals(groupId)) {
/* This is the same group as already notified to the system.
* Therefore do not change the device we have connected to the group,
* unless, previous one is disconnected now
@@ -869,10 +956,11 @@
if (mActiveAudioOutDevice.isConnected()) {
device = mActiveAudioOutDevice;
}
- } else if (previousGroupId != LE_AUDIO_GROUP_ID_INVALID) {
- Log.i(TAG, " Switching active group from " + previousGroupId + " to " + groupId);
+ } else if (deviceDescriptor.mGroupId != LE_AUDIO_GROUP_ID_INVALID) {
+ Log.i(TAG, " Switching active group from " + deviceDescriptor.mGroupId + " to "
+ + groupId);
/* Mark old group as no active */
- LeAudioGroupDescriptor descriptor = getGroupDescriptor(previousGroupId);
+ LeAudioGroupDescriptor descriptor = getGroupDescriptor(deviceDescriptor.mGroupId);
if (descriptor != null) {
descriptor.mIsActive = false;
}
@@ -1034,7 +1122,13 @@
int groupId = LE_AUDIO_GROUP_ID_INVALID;
if (device != null) {
- groupId = mDeviceGroupIdMap.getOrDefault(device, LE_AUDIO_GROUP_ID_INVALID);
+ LeAudioDeviceDescriptor descriptor = getDeviceDescriptor(device);
+ if (descriptor == null) {
+ Log.e(TAG, "setActiveGroupWithDevice: No valid descriptor for device: " + device);
+ return;
+ }
+
+ groupId = descriptor.mGroupId;
}
int currentlyActiveGroupId = getActiveGroupId();
@@ -1129,21 +1223,30 @@
}
void connectSet(BluetoothDevice device) {
- int groupId = getGroupId(device);
- if (groupId == LE_AUDIO_GROUP_ID_INVALID) {
+ LeAudioDeviceDescriptor descriptor = getDeviceDescriptor(device);
+ if (descriptor == null) {
+ Log.e(TAG, "connectSet: No valid descriptor for device: " + device);
+ return;
+ }
+ if (descriptor.mGroupId == LE_AUDIO_GROUP_ID_INVALID) {
return;
}
if (DBG) {
- Log.d(TAG, "connect() others from group id: " + groupId);
+ Log.d(TAG, "connect() others from group id: " + descriptor.mGroupId);
}
- for (BluetoothDevice storedDevice : mDeviceGroupIdMap.keySet()) {
+ Integer setGroupId = descriptor.mGroupId;
+
+ for (Map.Entry<BluetoothDevice, LeAudioDeviceDescriptor> entry
+ : mDeviceDescriptors.entrySet()) {
+ BluetoothDevice storedDevice = entry.getKey();
+ descriptor = entry.getValue();
if (device.equals(storedDevice)) {
continue;
}
- if (getGroupId(storedDevice) != groupId) {
+ if (!descriptor.mGroupId.equals(setGroupId)) {
continue;
}
@@ -1151,7 +1254,7 @@
Log.d(TAG, "connect(): " + storedDevice);
}
- synchronized (mStateMachines) {
+ synchronized (mGroupLock) {
LeAudioStateMachine sm = getOrCreateStateMachine(storedDevice);
if (sm == null) {
Log.e(TAG, "Ignored connect request for " + storedDevice
@@ -1199,8 +1302,15 @@
Log.d(TAG, "Clearing lost dev: " + descriptor.mLostLeadDeviceWhileStreaming);
}
- LeAudioStateMachine sm =
- mStateMachines.get(descriptor.mLostLeadDeviceWhileStreaming);
+ LeAudioDeviceDescriptor deviceDescriptor =
+ getDeviceDescriptor(descriptor.mLostLeadDeviceWhileStreaming);
+ if (deviceDescriptor == null) {
+ Log.e(TAG, "clearLostDevicesWhileStreaming: No valid descriptor for device: "
+ + descriptor.mLostLeadDeviceWhileStreaming);
+ return;
+ }
+
+ LeAudioStateMachine sm = deviceDescriptor.mStateMachine;
if (sm != null) {
LeAudioStackEvent stackEvent =
new LeAudioStackEvent(
@@ -1284,12 +1394,17 @@
void messageFromNative(LeAudioStackEvent stackEvent) {
Log.d(TAG, "Message from native: " + stackEvent);
BluetoothDevice device = stackEvent.device;
- Intent intent = null;
if (stackEvent.type == LeAudioStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED) {
// Some events require device state machine
- synchronized (mStateMachines) {
- LeAudioStateMachine sm = mStateMachines.get(device);
+ synchronized (mGroupLock) {
+ LeAudioDeviceDescriptor deviceDescriptor = getDeviceDescriptor(device);
+ if (deviceDescriptor == null) {
+ Log.e(TAG, "messageFromNative: No valid descriptor for device: " + device);
+ return;
+ }
+
+ LeAudioStateMachine sm = deviceDescriptor.mStateMachine;
if (sm != null) {
/*
* To improve scenario when lead Le Audio device is disconnected for the
@@ -1299,41 +1414,39 @@
* the hood and keep using lead device as a audio device indetifier in
* the audio framework in order to not stop the stream.
*/
- int groupId = getGroupId(device);
- synchronized (mGroupLock) {
- LeAudioGroupDescriptor descriptor = mGroupDescriptors.get(groupId);
- switch (stackEvent.valueInt1) {
- case LeAudioStackEvent.CONNECTION_STATE_DISCONNECTING:
- case LeAudioStackEvent.CONNECTION_STATE_DISCONNECTED:
- boolean disconnectDueToUnbond =
- (BluetoothDevice.BOND_NONE
- == mAdapterService.getBondState(device));
- if (descriptor != null && (Objects.equals(device,
- mActiveAudioOutDevice)
- || Objects.equals(device, mActiveAudioInDevice))
- && (getConnectedPeerDevices(groupId).size() > 1)
- && !disconnectDueToUnbond) {
+ int groupId = deviceDescriptor.mGroupId;
+ LeAudioGroupDescriptor descriptor = mGroupDescriptors.get(groupId);
+ switch (stackEvent.valueInt1) {
+ case LeAudioStackEvent.CONNECTION_STATE_DISCONNECTING:
+ case LeAudioStackEvent.CONNECTION_STATE_DISCONNECTED:
+ boolean disconnectDueToUnbond =
+ (BluetoothDevice.BOND_NONE
+ == mAdapterService.getBondState(device));
+ if (descriptor != null && (Objects.equals(device,
+ mActiveAudioOutDevice)
+ || Objects.equals(device, mActiveAudioInDevice))
+ && (getConnectedPeerDevices(groupId).size() > 1)
+ && !disconnectDueToUnbond) {
- if (DBG) Log.d(TAG, "Adding to lost devices : " + device);
- descriptor.mLostLeadDeviceWhileStreaming = device;
- return;
+ if (DBG) Log.d(TAG, "Adding to lost devices : " + device);
+ descriptor.mLostLeadDeviceWhileStreaming = device;
+ return;
+ }
+ break;
+ case LeAudioStackEvent.CONNECTION_STATE_CONNECTED:
+ case LeAudioStackEvent.CONNECTION_STATE_CONNECTING:
+ if (descriptor != null
+ && Objects.equals(
+ descriptor.mLostLeadDeviceWhileStreaming,
+ device)) {
+ if (DBG) {
+ Log.d(TAG, "Removing from lost devices : " + device);
}
- break;
- case LeAudioStackEvent.CONNECTION_STATE_CONNECTED:
- case LeAudioStackEvent.CONNECTION_STATE_CONNECTING:
- if (descriptor != null
- && Objects.equals(
- descriptor.mLostLeadDeviceWhileStreaming,
- device)) {
- if (DBG) {
- Log.d(TAG, "Removing from lost devices : " + device);
- }
- descriptor.mLostLeadDeviceWhileStreaming = null;
- /* Try to connect other devices from the group */
- connectSet(device);
- }
- break;
- }
+ descriptor.mLostLeadDeviceWhileStreaming = null;
+ /* Try to connect other devices from the group */
+ connectSet(device);
+ }
+ break;
}
} else {
/* state machine does not exist yet */
@@ -1411,26 +1524,36 @@
int src_audio_location = stackEvent.valueInt4;
int available_contexts = stackEvent.valueInt5;
- LeAudioGroupDescriptor descriptor = getGroupDescriptor(groupId);
- if (descriptor != null) {
- if (descriptor.mIsActive) {
- descriptor.mIsActive =
- updateActiveDevices(groupId, descriptor.mDirection, direction,
- descriptor.mIsActive);
- if (!descriptor.mIsActive) {
- notifyGroupStatusChanged(groupId, BluetoothLeAudio.GROUP_STATUS_INACTIVE);
+ synchronized (mGroupLock) {
+ LeAudioGroupDescriptor descriptor = getGroupDescriptor(groupId);
+ if (descriptor != null) {
+ if (descriptor.mIsActive) {
+ descriptor.mIsActive =
+ updateActiveDevices(groupId, descriptor.mDirection, direction,
+ descriptor.mIsActive);
+ if (!descriptor.mIsActive) {
+ notifyGroupStatusChanged(groupId,
+ BluetoothLeAudio.GROUP_STATUS_INACTIVE);
+ }
}
+ descriptor.mDirection = direction;
+ } else {
+ Log.e(TAG, "no descriptors for group: " + groupId);
}
- descriptor.mDirection = direction;
- } else {
- Log.e(TAG, "no descriptors for group: " + groupId);
}
} else if (stackEvent.type == LeAudioStackEvent.EVENT_TYPE_SINK_AUDIO_LOCATION_AVAILABLE) {
Objects.requireNonNull(stackEvent.device,
"Device should never be null, event: " + stackEvent);
int sink_audio_location = stackEvent.valueInt1;
- mDeviceAudioLocationMap.put(device, sink_audio_location);
+
+ LeAudioDeviceDescriptor descriptor = getDeviceDescriptor(device);
+ if (descriptor == null) {
+ Log.e(TAG, "messageFromNative: No valid descriptor for device: " + device);
+ return;
+ }
+
+ descriptor.mSinkAudioLocation = sink_audio_location;
if (DBG) {
Log.i(TAG, "EVENT_TYPE_SINK_AUDIO_LOCATION_AVAILABLE:" + device
@@ -1558,10 +1681,6 @@
setCcidInformation(userUuid, ccidInformation.first, ccidInformation.second);
}
}
-
- if (intent != null) {
- sendBroadcast(intent, BLUETOOTH_CONNECT);
- }
}
private LeAudioStateMachine getOrCreateStateMachine(BluetoothDevice device) {
@@ -1569,25 +1688,26 @@
Log.e(TAG, "getOrCreateStateMachine failed: device cannot be null");
return null;
}
- synchronized (mStateMachines) {
- LeAudioStateMachine sm = mStateMachines.get(device);
- if (sm != null) {
- return sm;
- }
- // Limit the maximum number of state machines to avoid DoS attack
- if (mStateMachines.size() >= MAX_LE_AUDIO_STATE_MACHINES) {
- Log.e(TAG, "Maximum number of LeAudio state machines reached: "
- + MAX_LE_AUDIO_STATE_MACHINES);
- return null;
- }
- if (DBG) {
- Log.d(TAG, "Creating a new state machine for " + device);
- }
- sm = LeAudioStateMachine.make(device, this,
- mLeAudioNativeInterface, mStateMachinesThread.getLooper());
- mStateMachines.put(device, sm);
+
+ LeAudioDeviceDescriptor descriptor = getDeviceDescriptor(device);
+ if (descriptor == null) {
+ Log.e(TAG, "getOrCreateStateMachine: No valid descriptor for device: " + device);
+ return null;
+ }
+
+ LeAudioStateMachine sm = descriptor.mStateMachine;
+ if (sm != null) {
return sm;
}
+
+ if (DBG) {
+ Log.d(TAG, "Creating a new state machine for " + device);
+ }
+
+ sm = LeAudioStateMachine.make(device, this,
+ mLeAudioNativeInterface, mStateMachinesThread.getLooper());
+ descriptor.mStateMachine = sm;
+ return sm;
}
// Remove state machine if the bonding for a device is removed
@@ -1624,16 +1744,23 @@
return;
}
- int groupId = getGroupId(device);
- if (groupId != LE_AUDIO_GROUP_ID_INVALID) {
- /* In case device is still in the group, let's remove it */
- mLeAudioNativeInterface.groupRemoveNode(groupId, device);
- }
+ synchronized (mGroupLock) {
+ LeAudioDeviceDescriptor descriptor = getDeviceDescriptor(device);
+ if (descriptor == null) {
+ Log.e(TAG, "bondStateChanged: No valid descriptor for device: " + device);
+ return;
+ }
- mDeviceGroupIdMap.remove(device);
- mDeviceAudioLocationMap.remove(device);
- synchronized (mStateMachines) {
- LeAudioStateMachine sm = mStateMachines.get(device);
+ if (descriptor.mGroupId != LE_AUDIO_GROUP_ID_INVALID) {
+ /* In case device is still in the group, let's remove it */
+ mLeAudioNativeInterface.groupRemoveNode(descriptor.mGroupId, device);
+ }
+
+ descriptor.mGroupId = LE_AUDIO_GROUP_ID_INVALID;
+ descriptor.mSinkAudioLocation = BluetoothLeAudio.AUDIO_LOCATION_INVALID;
+ descriptor.mDirection = AUDIO_DIRECTION_NONE;
+
+ LeAudioStateMachine sm = descriptor.mStateMachine;
if (sm == null) {
return;
}
@@ -1643,12 +1770,19 @@
return;
}
removeStateMachine(device);
+ mDeviceDescriptors.remove(device);
}
}
private void removeStateMachine(BluetoothDevice device) {
- synchronized (mStateMachines) {
- LeAudioStateMachine sm = mStateMachines.get(device);
+ synchronized (mGroupLock) {
+ LeAudioDeviceDescriptor descriptor = getDeviceDescriptor(device);
+ if (descriptor == null) {
+ Log.e(TAG, "removeStateMachine: No valid descriptor for device: " + device);
+ return;
+ }
+
+ LeAudioStateMachine sm = descriptor.mStateMachine;
if (sm == null) {
Log.w(TAG, "removeStateMachine: device " + device
+ " does not have a state machine");
@@ -1657,7 +1791,7 @@
Log.i(TAG, "removeStateMachine: removing state machine for device: " + device);
sm.doQuit();
sm.cleanup();
- mStateMachines.remove(device);
+ descriptor.mStateMachine = null;
}
}
@@ -1679,21 +1813,27 @@
+ " fromState=" + fromState + " toState=" + toState);
return;
}
+
+ LeAudioDeviceDescriptor deviceDescriptor = getDeviceDescriptor(device);
+ if (deviceDescriptor == null) {
+ Log.e(TAG, "connectionStateChanged: No valid descriptor for device: " + device);
+ return;
+ }
+
if (toState == BluetoothProfile.STATE_CONNECTED) {
- int myGroupId = getGroupId(device);
- if (myGroupId == LE_AUDIO_GROUP_ID_INVALID
- || getConnectedPeerDevices(myGroupId).size() == 1) {
+ if (deviceDescriptor.mGroupId == LE_AUDIO_GROUP_ID_INVALID
+ || getConnectedPeerDevices(deviceDescriptor.mGroupId).size() == 1) {
// Log LE Audio connection event if we are the first device in a set
// Or when the GroupId has not been found
// MetricsLogger.logProfileConnectionEvent(
// BluetoothMetricsProto.ProfileId.LE_AUDIO);
}
- LeAudioGroupDescriptor descriptor = getGroupDescriptor(myGroupId);
+ LeAudioGroupDescriptor descriptor = getGroupDescriptor(deviceDescriptor.mGroupId);
if (descriptor != null) {
descriptor.mIsConnected = true;
} else {
- Log.e(TAG, "no descriptors for group: " + myGroupId);
+ Log.e(TAG, "no descriptors for group: " + deviceDescriptor.mGroupId);
}
}
// Check if the device is disconnected - if unbond, remove the state machine
@@ -1706,14 +1846,14 @@
removeStateMachine(device);
}
- int myGroupId = getGroupId(device);
- LeAudioGroupDescriptor descriptor = getGroupDescriptor(myGroupId);
+ LeAudioGroupDescriptor descriptor = getGroupDescriptor(deviceDescriptor.mGroupId);
if (descriptor == null) {
- Log.e(TAG, "no descriptors for group: " + myGroupId);
+ Log.e(TAG, "no descriptors for group: " + deviceDescriptor.mGroupId);
return;
}
- List<BluetoothDevice> connectedDevices = getConnectedPeerDevices(myGroupId);
+ List<BluetoothDevice> connectedDevices =
+ getConnectedPeerDevices(deviceDescriptor.mGroupId);
/* Let's check if the last connected device is really connected */
if (connectedDevices.size() == 1 && Objects.equals(
connectedDevices.get(0), descriptor.mLostLeadDeviceWhileStreaming)) {
@@ -1721,14 +1861,14 @@
return;
}
- if (getConnectedPeerDevices(myGroupId).isEmpty()) {
+ if (getConnectedPeerDevices(deviceDescriptor.mGroupId).isEmpty()) {
descriptor.mIsConnected = false;
if (descriptor.mIsActive) {
/* Notify Native layer */
setActiveDevice(null);
descriptor.mIsActive = false;
/* Update audio framework */
- updateActiveDevices(myGroupId,
+ updateActiveDevices(deviceDescriptor.mGroupId,
descriptor.mDirection,
descriptor.mDirection,
descriptor.mIsActive);
@@ -1737,7 +1877,7 @@
}
if (descriptor.mIsActive) {
- updateActiveDevices(myGroupId,
+ updateActiveDevices(deviceDescriptor.mGroupId,
descriptor.mDirection,
descriptor.mDirection,
descriptor.mIsActive);
@@ -1834,8 +1974,14 @@
if (device == null) {
return BluetoothLeAudio.AUDIO_LOCATION_INVALID;
}
- return mDeviceAudioLocationMap.getOrDefault(device,
- BluetoothLeAudio.AUDIO_LOCATION_INVALID);
+
+ LeAudioDeviceDescriptor descriptor = getDeviceDescriptor(device);
+ if (descriptor == null) {
+ Log.e(TAG, "getAudioLocation: No valid descriptor for device: " + device);
+ return BluetoothLeAudio.AUDIO_LOCATION_INVALID;
+ }
+
+ return descriptor.mSinkAudioLocation;
}
/**
@@ -1926,8 +2072,15 @@
if (device == null) {
return LE_AUDIO_GROUP_ID_INVALID;
}
+
synchronized (mGroupLock) {
- return mDeviceGroupIdMap.getOrDefault(device, LE_AUDIO_GROUP_ID_INVALID);
+ LeAudioDeviceDescriptor descriptor = getDeviceDescriptor(device);
+ if (descriptor == null) {
+ Log.e(TAG, "getGroupId: No valid descriptor for device: " + device);
+ return LE_AUDIO_GROUP_ID_INVALID;
+ }
+
+ return descriptor.mGroupId;
}
}
@@ -1999,7 +2152,7 @@
mBluetoothEnabled = true;
synchronized (mGroupLock) {
- if (mDeviceGroupIdMap.isEmpty()) {
+ if (mDeviceDescriptors.isEmpty()) {
return;
}
}
@@ -2011,7 +2164,12 @@
}
synchronized (mGroupLock) {
- for (Map.Entry<BluetoothDevice, Integer> entry : mDeviceGroupIdMap.entrySet()) {
+ for (Map.Entry<BluetoothDevice, LeAudioDeviceDescriptor> entry
+ : mDeviceDescriptors.entrySet()) {
+ if (entry.getValue().mGroupId == LE_AUDIO_GROUP_ID_INVALID) {
+ continue;
+ }
+
mcpService.setDeviceAuthorized(entry.getKey(), true);
}
}
@@ -2023,13 +2181,35 @@
}
}
+ private LeAudioDeviceDescriptor getDeviceDescriptor(BluetoothDevice device) {
+ synchronized (mGroupLock) {
+ return mDeviceDescriptors.get(device);
+ }
+ }
+
private void handleGroupNodeAdded(BluetoothDevice device, int groupId) {
synchronized (mGroupLock) {
if (DBG) {
Log.d(TAG, "Device " + device + " added to group " + groupId);
}
- mDeviceGroupIdMap.put(device, groupId);
+ LeAudioDeviceDescriptor deviceDescriptor = getDeviceDescriptor(device);
+ if (deviceDescriptor == null) {
+ deviceDescriptor = createDeviceDescriptor(device);
+ if (deviceDescriptor == null) {
+ Log.e(TAG, "handleGroupNodeAdded: Can't create descriptor for added from"
+ + " storage device: " + device);
+ return;
+ }
+
+ LeAudioStateMachine sm = getOrCreateStateMachine(device);
+ if (getOrCreateStateMachine(device) == null) {
+ Log.e(TAG, "Can't get state machine for device: " + device);
+ return;
+ }
+ }
+ deviceDescriptor.mGroupId = groupId;
+
LeAudioGroupDescriptor descriptor = mGroupDescriptors.get(groupId);
if (descriptor == null) {
mGroupDescriptors.put(groupId, new LeAudioGroupDescriptor());
@@ -2071,17 +2251,39 @@
Log.d(TAG, "Removing device " + device + " grom group " + groupId);
}
- LeAudioGroupDescriptor descriptor = getGroupDescriptor(groupId);
- if (DBG) {
- Log.d(TAG, "Lost lead device is " + descriptor.mLostLeadDeviceWhileStreaming);
- }
- if (Objects.equals(device, descriptor.mLostLeadDeviceWhileStreaming)) {
- clearLostDevicesWhileStreaming(descriptor);
- }
-
synchronized (mGroupLock) {
- mDeviceGroupIdMap.remove(device);
- if (!mDeviceGroupIdMap.containsValue(groupId)) {
+ LeAudioGroupDescriptor groupDescriptor = getGroupDescriptor(groupId);
+ if (DBG) {
+ Log.d(TAG, "Lost lead device is " + groupDescriptor.mLostLeadDeviceWhileStreaming);
+ }
+ if (Objects.equals(device, groupDescriptor.mLostLeadDeviceWhileStreaming)) {
+ clearLostDevicesWhileStreaming(groupDescriptor);
+ }
+
+ LeAudioDeviceDescriptor deviceDescriptor = getDeviceDescriptor(device);
+ if (deviceDescriptor == null) {
+ Log.e(TAG, "handleGroupNodeRemoved: No valid descriptor for device: " + device);
+ return;
+ }
+ deviceDescriptor.mGroupId = LE_AUDIO_GROUP_ID_INVALID;
+
+ boolean isGroupEmpty = true;
+
+ for (LeAudioDeviceDescriptor descriptor : mDeviceDescriptors.values()) {
+ if (descriptor.mGroupId == groupId) {
+ isGroupEmpty = false;
+ break;
+ }
+ }
+
+ if (isGroupEmpty) {
+ /* Device is currently an active device. Group needs to be inactivated before
+ * removing
+ */
+ if (Objects.equals(device, mActiveAudioOutDevice)
+ || Objects.equals(device, mActiveAudioInDevice)) {
+ handleGroupTransitToInactive(groupId);
+ }
mGroupDescriptors.remove(groupId);
}
notifyGroupNodeRemoved(device, groupId);
@@ -2911,16 +3113,22 @@
@Override
public void dump(StringBuilder sb) {
super.dump(sb);
- ProfileService.println(sb, "State machines: ");
- for (LeAudioStateMachine sm : mStateMachines.values()) {
- sm.dump(sb);
- }
ProfileService.println(sb, "Active Groups information: ");
ProfileService.println(sb, " currentlyActiveGroupId: " + getActiveGroupId());
ProfileService.println(sb, " mActiveAudioOutDevice: " + mActiveAudioOutDevice);
ProfileService.println(sb, " mActiveAudioInDevice: " + mActiveAudioInDevice);
ProfileService.println(sb, " mHfpHandoverDevice:" + mHfpHandoverDevice);
+ for (Map.Entry<BluetoothDevice, LeAudioDeviceDescriptor> entry
+ : mDeviceDescriptors.entrySet()) {
+ LeAudioDeviceDescriptor descriptor = entry.getValue();
+
+ descriptor.mStateMachine.dump(sb);
+ ProfileService.println(sb, " mGroupId: " + descriptor.mGroupId);
+ ProfileService.println(sb, " mSinkAudioLocation: " + descriptor.mSinkAudioLocation);
+ ProfileService.println(sb, " mDirection: " + descriptor.mDirection);
+ }
+
for (Map.Entry<Integer, LeAudioGroupDescriptor> entry : mGroupDescriptors.entrySet()) {
LeAudioGroupDescriptor descriptor = entry.getValue();
Integer groupId = entry.getKey();
diff --git a/android/app/src/com/android/bluetooth/le_audio/LeAudioStateMachine.java b/android/app/src/com/android/bluetooth/le_audio/LeAudioStateMachine.java
index a838cbb..95c4e0f 100644
--- a/android/app/src/com/android/bluetooth/le_audio/LeAudioStateMachine.java
+++ b/android/app/src/com/android/bluetooth/le_audio/LeAudioStateMachine.java
@@ -55,8 +55,6 @@
import android.util.Log;
import static android.Manifest.permission.BLUETOOTH_CONNECT;
-import android.annotation.RequiresPermission;
-
import com.android.bluetooth.Utils;
import com.android.bluetooth.btservice.ProfileService;
import com.android.internal.annotations.VisibleForTesting;
diff --git a/android/app/src/com/android/bluetooth/map/BluetoothMapContent.java b/android/app/src/com/android/bluetooth/map/BluetoothMapContent.java
index bf31e43..1c684bf 100644
--- a/android/app/src/com/android/bluetooth/map/BluetoothMapContent.java
+++ b/android/app/src/com/android/bluetooth/map/BluetoothMapContent.java
@@ -40,6 +40,7 @@
import com.android.bluetooth.BluetoothMethodProxy;
import com.android.bluetooth.DeviceWorkArounds;
import com.android.bluetooth.SignedLongLong;
+import com.android.bluetooth.Utils;
import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
import com.android.bluetooth.map.BluetoothMapbMessageMime.MimePart;
import com.android.bluetooth.mapapi.BluetoothMapContract;
@@ -223,7 +224,8 @@
};
/* CONVO LISTING projections and column indexes */
- private static final String[] MMS_SMS_THREAD_PROJECTION = {
+ @VisibleForTesting
+ static final String[] MMS_SMS_THREAD_PROJECTION = {
Threads._ID,
Threads.DATE,
Threads.SNIPPET,
@@ -1338,9 +1340,15 @@
}
// Fix Subject Display issue with HONDA Carkit - Ignore subject Mask.
- if (DeviceWorkArounds.addressStartsWith(BluetoothMapService.getRemoteDevice().getAddress(),
- DeviceWorkArounds.HONDA_CARKIT)
- || (ap.getParameterMask() & MASK_SUBJECT) != 0) {
+ boolean isHondaCarkit;
+ if (Utils.isInstrumentationTestMode()) {
+ isHondaCarkit = false;
+ } else {
+ isHondaCarkit = DeviceWorkArounds.addressStartsWith(
+ BluetoothMapService.getRemoteDevice().getAddress(),
+ DeviceWorkArounds.HONDA_CARKIT);
+ }
+ if (isHondaCarkit || (ap.getParameterMask() & MASK_SUBJECT) != 0) {
if (fi.mMsgType == FilterInfo.TYPE_SMS) {
subject = c.getString(fi.mSmsColSubject);
} else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
@@ -2283,7 +2291,8 @@
if (D) {
Log.d(TAG, "msgType: " + fi.mMsgType + " where: " + where);
}
- smsCursor = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION, where, null,
+ smsCursor = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
+ Sms.CONTENT_URI, SMS_PROJECTION, where, null,
Sms.DATE + " DESC" + limit);
if (smsCursor != null) {
BluetoothMapMessageListingElement e = null;
@@ -2325,7 +2334,8 @@
if (D) {
Log.d(TAG, "msgType: " + fi.mMsgType + " where: " + where);
}
- mmsCursor = mResolver.query(Mms.CONTENT_URI, MMS_PROJECTION, where, null,
+ mmsCursor = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
+ Mms.CONTENT_URI, MMS_PROJECTION, where, null,
Mms.DATE + " DESC" + limit);
if (mmsCursor != null) {
BluetoothMapMessageListingElement e = null;
@@ -2368,10 +2378,9 @@
Log.d(TAG, "msgType: " + fi.mMsgType + " where: " + where);
}
Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
- emailCursor =
- mResolver.query(contentUri, BluetoothMapContract.BT_MESSAGE_PROJECTION,
- where, null,
- BluetoothMapContract.MessageColumns.DATE + " DESC" + limit);
+ emailCursor = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
+ contentUri, BluetoothMapContract.BT_MESSAGE_PROJECTION, where, null,
+ BluetoothMapContract.MessageColumns.DATE + " DESC" + limit);
if (emailCursor != null) {
BluetoothMapMessageListingElement e = null;
// store column index so we dont have to look them up anymore (optimization)
@@ -2412,8 +2421,8 @@
}
Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
- imCursor = mResolver.query(contentUri,
- BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION, where, null,
+ imCursor = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
+ contentUri, BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION, where, null,
BluetoothMapContract.MessageColumns.DATE + " DESC" + limit);
if (imCursor != null) {
BluetoothMapMessageListingElement e = null;
@@ -2521,8 +2530,8 @@
if (smsSelected(fi, ap) && folderElement.hasSmsMmsContent()) {
fi.mMsgType = FilterInfo.TYPE_SMS;
String where = setWhereFilter(folderElement, fi, ap);
- Cursor c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION, where, null,
- Sms.DATE + " DESC");
+ Cursor c = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
+ Sms.CONTENT_URI, SMS_PROJECTION, where, null, Sms.DATE + " DESC");
try {
if (c != null) {
cnt = c.getCount();
@@ -2537,8 +2546,8 @@
if (mmsSelected(ap) && folderElement.hasSmsMmsContent()) {
fi.mMsgType = FilterInfo.TYPE_MMS;
String where = setWhereFilter(folderElement, fi, ap);
- Cursor c = mResolver.query(Mms.CONTENT_URI, MMS_PROJECTION, where, null,
- Mms.DATE + " DESC");
+ Cursor c = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
+ Mms.CONTENT_URI, MMS_PROJECTION, where, null, Mms.DATE + " DESC");
try {
if (c != null) {
cnt += c.getCount();
@@ -2555,8 +2564,9 @@
String where = setWhereFilter(folderElement, fi, ap);
if (!where.isEmpty()) {
Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
- Cursor c = mResolver.query(contentUri, BluetoothMapContract.BT_MESSAGE_PROJECTION,
- where, null, BluetoothMapContract.MessageColumns.DATE + " DESC");
+ Cursor c = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
+ contentUri, BluetoothMapContract.BT_MESSAGE_PROJECTION, where, null,
+ BluetoothMapContract.MessageColumns.DATE + " DESC");
try {
if (c != null) {
cnt += c.getCount();
@@ -2574,8 +2584,8 @@
String where = setWhereFilter(folderElement, fi, ap);
if (!where.isEmpty()) {
Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
- Cursor c = mResolver.query(contentUri,
- BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION, where, null,
+ Cursor c = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
+ contentUri, BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION, where, null,
BluetoothMapContract.MessageColumns.DATE + " DESC");
try {
if (c != null) {
@@ -2617,8 +2627,8 @@
String where = setWhereFilterFolderType(folderElement, fi);
where += " AND " + Sms.READ + "=0 ";
where += setWhereFilterPeriod(ap, fi);
- Cursor c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION, where, null,
- Sms.DATE + " DESC");
+ Cursor c = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
+ Sms.CONTENT_URI, SMS_PROJECTION, where, null, Sms.DATE + " DESC");
try {
if (c != null) {
cnt = c.getCount();
@@ -2635,8 +2645,8 @@
String where = setWhereFilterFolderType(folderElement, fi);
where += " AND " + Mms.READ + "=0 ";
where += setWhereFilterPeriod(ap, fi);
- Cursor c = mResolver.query(Mms.CONTENT_URI, MMS_PROJECTION, where, null,
- Sms.DATE + " DESC");
+ Cursor c = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
+ Mms.CONTENT_URI, MMS_PROJECTION, where, null, Sms.DATE + " DESC");
try {
if (c != null) {
cnt += c.getCount();
@@ -2656,8 +2666,9 @@
where += " AND " + BluetoothMapContract.MessageColumns.FLAG_READ + "=0 ";
where += setWhereFilterPeriod(ap, fi);
Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
- Cursor c = mResolver.query(contentUri, BluetoothMapContract.BT_MESSAGE_PROJECTION,
- where, null, BluetoothMapContract.MessageColumns.DATE + " DESC");
+ Cursor c = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
+ contentUri, BluetoothMapContract.BT_MESSAGE_PROJECTION, where, null,
+ BluetoothMapContract.MessageColumns.DATE + " DESC");
try {
if (c != null) {
cnt += c.getCount();
@@ -2677,8 +2688,8 @@
where += " AND " + BluetoothMapContract.MessageColumns.FLAG_READ + "=0 ";
where += setWhereFilterPeriod(ap, fi);
Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
- Cursor c = mResolver.query(contentUri,
- BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION, where, null,
+ Cursor c = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
+ contentUri, BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION, where, null,
BluetoothMapContract.MessageColumns.DATE + " DESC");
try {
if (c != null) {
@@ -2794,7 +2805,8 @@
}
// TODO: Optimize: Reduce projection based on convo parameter mask
smsMmsCursor =
- mResolver.query(uri, MMS_SMS_THREAD_PROJECTION, selection.toString(), args,
+ BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver, uri,
+ MMS_SMS_THREAD_PROJECTION, selection.toString(), null,
sortOrder.toString());
if (smsMmsCursor != null) {
// store column index so we don't have to look them up anymore (optimization)
@@ -2855,13 +2867,12 @@
Log.v(TAG, "URI with parameters: " + contentUri.toString());
}
// TODO: Optimize: Reduce projection based on convo parameter mask
- imEmailCursor =
- mResolver.query(contentUri, BluetoothMapContract.BT_CONVERSATION_PROJECTION,
- null, null,
- BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY
- + " DESC, "
- + BluetoothMapContract.ConversationColumns.THREAD_ID
- + " ASC");
+ imEmailCursor = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
+ contentUri, BluetoothMapContract.BT_CONVERSATION_PROJECTION, null, null,
+ BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY
+ + " DESC, "
+ + BluetoothMapContract.ConversationColumns.THREAD_ID
+ + " ASC");
if (imEmailCursor != null) {
BluetoothMapConvoListingElement e = null;
// store column index so we don't have to look them up anymore (optimization)
diff --git a/android/app/src/com/android/bluetooth/map/BluetoothMapContentObserver.java b/android/app/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
index 62bf498..8179da3 100644
--- a/android/app/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
+++ b/android/app/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
@@ -81,6 +81,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.TimeUnit;
@TargetApi(19)
public class BluetoothMapContentObserver {
@@ -142,10 +143,12 @@
private BluetoothMapMasInstance mMasInstance = null;
private int mMasId;
private boolean mEnableSmsMms = false;
- private boolean mObserverRegistered = false;
+ @VisibleForTesting
+ boolean mObserverRegistered = false;
@VisibleForTesting
BluetoothMapAccountItem mAccount;
- private String mAuthority = null;
+ @VisibleForTesting
+ String mAuthority = null;
// Default supported feature bit mask is 0x1f
private int mMapSupportedFeatures = BluetoothMapUtils.MAP_FEATURE_DEFAULT_BITMASK;
@@ -1262,7 +1265,8 @@
}
}
- private void initMsgList() throws RemoteException {
+ @VisibleForTesting
+ void initMsgList() throws RemoteException {
if (V) {
Log.d(TAG, "initMsgList");
}
@@ -1276,7 +1280,8 @@
Cursor c;
try {
- c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION_SHORT, null, null, null);
+ c = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
+ Sms.CONTENT_URI, SMS_PROJECTION_SHORT, null, null, null);
} catch (SQLiteException e) {
Log.e(TAG, "Failed to initialize the list of messages: " + e.toString());
return;
@@ -1307,7 +1312,8 @@
HashMap<Long, Msg> msgListMms = new HashMap<Long, Msg>();
- c = mResolver.query(Mms.CONTENT_URI, MMS_PROJECTION_SHORT, null, null, null);
+ c = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver, Mms.CONTENT_URI,
+ MMS_PROJECTION_SHORT, null, null, null);
try {
if (c != null && c.moveToFirst()) {
do {
@@ -1417,7 +1423,8 @@
}
}
- private void handleMsgListChangesSms() {
+ @VisibleForTesting
+ void handleMsgListChangesSms() {
if (V) {
Log.d(TAG, "handleMsgListChangesSms");
}
@@ -1428,9 +1435,11 @@
Cursor c;
synchronized (getMsgListSms()) {
if (mMapEventReportVersion == BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
- c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION_SHORT, null, null, null);
+ c = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
+ Sms.CONTENT_URI, SMS_PROJECTION_SHORT, null, null, null);
} else {
- c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION_SHORT_EXT, null, null, null);
+ c = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
+ Sms.CONTENT_URI, SMS_PROJECTION_SHORT_EXT, null, null, null);
}
try {
if (c != null && c.moveToFirst()) {
@@ -1462,7 +1471,7 @@
long timestamp = c.getLong(c.getColumnIndex(Sms.DATE));
String date = BluetoothMapUtils.getDateTimeString(timestamp);
if (BluetoothMapUtils.isDateTimeOlderThanOneYear(timestamp)) {
- // Skip sending new message events older than one year
+ // Skip sending message events older than one year
listChanged = false;
msgListSms.remove(id);
continue;
@@ -1625,7 +1634,6 @@
if (msg == null) {
/* New message - only notify on retrieve conf */
- listChanged = true;
if (getMmsFolderName(type).equalsIgnoreCase(
BluetoothMapContract.FOLDER_NAME_INBOX)
&& mtype != MESSAGE_TYPE_RETRIEVE_CONF) {
@@ -1637,11 +1645,13 @@
if (mTransmitEvents && // extract contact details only if needed
mMapEventReportVersion
!= BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
- long timestamp = c.getLong(c.getColumnIndex(Mms.DATE));
+ // MMS date field is in seconds
+ long timestamp =
+ TimeUnit.SECONDS.toMillis(
+ c.getLong(c.getColumnIndex(Mms.DATE)));
String date = BluetoothMapUtils.getDateTimeString(timestamp);
if (BluetoothMapUtils.isDateTimeOlderThanOneYear(timestamp)) {
// Skip sending new message events older than one year
- listChanged = false;
msgListMms.remove(id);
continue;
}
@@ -1685,6 +1695,7 @@
evt = new Event(EVENT_TYPE_NEW, id, getMmsFolderName(type), null,
TYPE.MMS);
}
+ listChanged = true;
sendEvent(evt);
} else {
@@ -1975,7 +1986,8 @@
}
}
- private void handleContactListChanges(Uri uri) {
+ @VisibleForTesting
+ void handleContactListChanges(Uri uri) {
if (uri.getAuthority().equals(mAuthority)) {
try {
if (V) {
@@ -2951,7 +2963,8 @@
if (handle != -1) {
String whereClause = " _id= " + handle;
Uri uri = Mms.CONTENT_URI;
- Cursor queryResult = resolver.query(uri, null, whereClause, null, null);
+ Cursor queryResult = BluetoothMethodProxy.getInstance().contentResolverQuery(resolver,
+ uri, null, whereClause, null, null);
try {
if (queryResult != null) {
if (queryResult.getCount() > 0) {
@@ -2959,7 +2972,8 @@
ContentValues data = new ContentValues();
/* set folder to be outbox */
data.put(Mms.MESSAGE_BOX, folder);
- resolver.update(uri, data, whereClause, null);
+ BluetoothMethodProxy.getInstance().contentResolverUpdate(resolver, uri,
+ data, whereClause, null);
if (D) {
Log.d(TAG, "moved MMS message to " + getMmsFolderName(folder));
}
@@ -3578,7 +3592,7 @@
if (D) {
Log.d(TAG, "Transparent in use - delete");
}
- resolver.delete(uri, null, null);
+ BluetoothMethodProxy.getInstance().contentResolverDelete(resolver, uri, null, null);
} else if (result == Activity.RESULT_OK) {
/* This will trigger a notification */
moveMmsToFolder(handle, resolver, Mms.MESSAGE_BOX_SENT);
@@ -3653,7 +3667,7 @@
/* Delete from DB */
ContentResolver resolver = context.getContentResolver();
if (resolver != null) {
- resolver.delete(uri, null, null);
+ BluetoothMethodProxy.getInstance().contentResolverDelete(resolver, uri, null, null);
} else {
Log.w(TAG, "Unable to get resolver");
}
diff --git a/android/app/src/com/android/bluetooth/map/BluetoothMapObexServer.java b/android/app/src/com/android/bluetooth/map/BluetoothMapObexServer.java
index f855c78..c02fe4a 100644
--- a/android/app/src/com/android/bluetooth/map/BluetoothMapObexServer.java
+++ b/android/app/src/com/android/bluetooth/map/BluetoothMapObexServer.java
@@ -1308,7 +1308,8 @@
* @param overwrite True: The msgType will be overwritten to match the message types supported
* by this MAS instance. False: any unsupported message types will be masked out.
*/
- private void setMsgTypeFilterParams(BluetoothMapAppParams appParams, boolean overwrite) {
+ @VisibleForTesting
+ void setMsgTypeFilterParams(BluetoothMapAppParams appParams, boolean overwrite) {
int masFilterMask = 0;
if (!mEnableSmsMms) {
masFilterMask |= BluetoothMapAppParams.FILTER_NO_SMS_CDMA;
diff --git a/android/app/src/com/android/bluetooth/map/BluetoothMapUtils.java b/android/app/src/com/android/bluetooth/map/BluetoothMapUtils.java
index 7d7047f..5d26238 100644
--- a/android/app/src/com/android/bluetooth/map/BluetoothMapUtils.java
+++ b/android/app/src/com/android/bluetooth/map/BluetoothMapUtils.java
@@ -682,10 +682,10 @@
cal.setTimeInMillis(timestamp);
Calendar oneYearAgo = Calendar.getInstance();
oneYearAgo.add(Calendar.YEAR, -1);
- if (cal.compareTo(oneYearAgo) > 0) {
+ if (cal.before(oneYearAgo)) {
if (V) {
- Log.v(TAG, "isDateTimeOlderThanOneYear timestamp : " + timestamp
- + " oneYearAgo: " + oneYearAgo);
+ Log.v(TAG, "isDateTimeOlderThanOneYear " + cal.getTimeInMillis()
+ + " oneYearAgo: " + oneYearAgo.getTimeInMillis());
}
return true;
}
diff --git a/android/app/src/com/android/bluetooth/map/SmsMmsContacts.java b/android/app/src/com/android/bluetooth/map/SmsMmsContacts.java
index b02d148..b00bd24 100644
--- a/android/app/src/com/android/bluetooth/map/SmsMmsContacts.java
+++ b/android/app/src/com/android/bluetooth/map/SmsMmsContacts.java
@@ -49,7 +49,8 @@
private static final Uri ADDRESS_URI =
MmsSms.CONTENT_URI.buildUpon().appendPath("canonical-addresses").build();
- private static final String[] ADDRESS_PROJECTION = {
+ @VisibleForTesting
+ static final String[] ADDRESS_PROJECTION = {
CanonicalAddressesColumns._ID, CanonicalAddressesColumns.ADDRESS
};
private static final int COL_ADDR_ID =
@@ -57,7 +58,8 @@
private static final int COL_ADDR_ADDR =
Arrays.asList(ADDRESS_PROJECTION).indexOf(CanonicalAddressesColumns.ADDRESS);
- private static final String[] CONTACT_PROJECTION = {Contacts._ID, Contacts.DISPLAY_NAME};
+ @VisibleForTesting
+ static final String[] CONTACT_PROJECTION = {Contacts._ID, Contacts.DISPLAY_NAME};
private static final String CONTACT_SEL_VISIBLE = Contacts.IN_VISIBLE_GROUP + "=1";
private static final int COL_CONTACT_ID =
Arrays.asList(CONTACT_PROJECTION).indexOf(Contacts._ID);
diff --git a/android/app/src/com/android/bluetooth/mapclient/MapClientContent.java b/android/app/src/com/android/bluetooth/mapclient/MapClientContent.java
index 36d4535..aa60b5b 100644
--- a/android/app/src/com/android/bluetooth/mapclient/MapClientContent.java
+++ b/android/app/src/com/android/bluetooth/mapclient/MapClientContent.java
@@ -137,6 +137,10 @@
SubscriptionManager subscriptionManager =
context.getSystemService(SubscriptionManager.class);
List<SubscriptionInfo> subscriptions = subscriptionManager.getActiveSubscriptionInfoList();
+ if (subscriptions == null) {
+ Log.w(TAG, "Active subscription list is missing");
+ return;
+ }
for (SubscriptionInfo info : subscriptions) {
if (info.getSubscriptionType() == SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM) {
clearMessages(context, info.getSubscriptionId());
diff --git a/android/app/src/com/android/bluetooth/mapclient/MapClientService.java b/android/app/src/com/android/bluetooth/mapclient/MapClientService.java
index 40bf81c..c1daa8c 100644
--- a/android/app/src/com/android/bluetooth/mapclient/MapClientService.java
+++ b/android/app/src/com/android/bluetooth/mapclient/MapClientService.java
@@ -339,9 +339,11 @@
Log.d(TAG, "stop()");
}
- mAdapterService.notifyActivityAttributionInfo(
- getAttributionSource(),
- AdapterService.ACTIVITY_ATTRIBUTION_NO_ACTIVE_DEVICE_ADDRESS);
+ if (mAdapterService != null) {
+ mAdapterService.notifyActivityAttributionInfo(
+ getAttributionSource(),
+ AdapterService.ACTIVITY_ATTRIBUTION_NO_ACTIVE_DEVICE_ADDRESS);
+ }
if (mMapReceiver != null) {
unregisterReceiver(mMapReceiver);
mMapReceiver = null;
@@ -355,6 +357,7 @@
}
stateMachine.doQuit();
}
+ mMapInstanceMap.clear();
return true;
}
@@ -364,7 +367,6 @@
Log.d(TAG, "in Cleanup");
}
removeUncleanAccounts();
- mMapInstanceMap.clear();
// TODO(b/72948646): should be moved to stop()
setMapClientService(null);
}
diff --git a/android/app/src/com/android/bluetooth/mapclient/MnsObexServer.java b/android/app/src/com/android/bluetooth/mapclient/MnsObexServer.java
index 6c0d536..accdbfb 100644
--- a/android/app/src/com/android/bluetooth/mapclient/MnsObexServer.java
+++ b/android/app/src/com/android/bluetooth/mapclient/MnsObexServer.java
@@ -20,6 +20,7 @@
import com.android.bluetooth.ObexAppParameters;
import com.android.bluetooth.ObexServerSockets;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.obex.HeaderSet;
import com.android.obex.Operation;
import com.android.obex.ResponseCodes;
@@ -34,7 +35,8 @@
private static final String TAG = "MnsObexServer";
private static final boolean VDBG = MapClientService.VDBG;
- private static final byte[] MNS_TARGET = new byte[]{
+ @VisibleForTesting
+ static final byte[] MNS_TARGET = new byte[]{
(byte) 0xbb,
0x58,
0x2b,
@@ -53,7 +55,8 @@
0x66
};
- private static final String TYPE = "x-bt/MAP-event-report";
+ @VisibleForTesting
+ static final String TYPE = "x-bt/MAP-event-report";
private final WeakReference<MceStateMachine> mStateMachineReference;
private final ObexServerSockets mObexServerSockets;
diff --git a/android/app/src/com/android/bluetooth/mcp/MediaControlGattService.java b/android/app/src/com/android/bluetooth/mcp/MediaControlGattService.java
index 0f1fef7..b37cbaa 100644
--- a/android/app/src/com/android/bluetooth/mcp/MediaControlGattService.java
+++ b/android/app/src/com/android/bluetooth/mcp/MediaControlGattService.java
@@ -17,6 +17,7 @@
package com.android.bluetooth.mcp;
+import static android.bluetooth.BluetoothDevice.METADATA_GMCS_CCCD;
import static android.bluetooth.BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED;
import static android.bluetooth.BluetoothGattCharacteristic.PERMISSION_WRITE_ENCRYPTED;
import static android.bluetooth.BluetoothGattCharacteristic.PROPERTY_NOTIFY;
@@ -28,6 +29,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
@@ -37,12 +39,17 @@
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
+import android.bluetooth.IBluetoothManager;
+import android.bluetooth.IBluetoothStateChangeCallback;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
+import android.os.ParcelUuid;
+import android.os.RemoteException;
import android.util.Log;
import android.util.Pair;
+import com.android.bluetooth.Utils;
import com.android.bluetooth.a2dp.A2dpService;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.hearingaid.HearingAidService;
@@ -441,7 +448,7 @@
} else {
status = BluetoothGatt.GATT_SUCCESS;
setCcc(device, op.mDescriptor.getCharacteristic().getUuid(), op.mOffset,
- op.mValue);
+ op.mValue, true);
}
if (op.mResponseNeeded) {
@@ -522,6 +529,34 @@
}
}
+ private void restoreCccValuesForStoredDevices() {
+ for (BluetoothDevice device : mAdapterService.getBondedDevices()) {
+ byte[] gmcs_cccd = device.getMetadata(METADATA_GMCS_CCCD);
+
+ if ((gmcs_cccd == null) || (gmcs_cccd.length == 0)) {
+ return;
+ }
+
+ List<ParcelUuid> uuidList = Arrays.asList(Utils.byteArrayToUuid(gmcs_cccd));
+
+ /* Restore CCCD values for device */
+ for (ParcelUuid uuid : uuidList) {
+ setCcc(device, uuid.getUuid(), 0,
+ BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE, false);
+ }
+ }
+ }
+
+ private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
+ new IBluetoothStateChangeCallback.Stub() {
+ public void onBluetoothStateChange(boolean up) {
+ if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
+ if (up) {
+ restoreCccValuesForStoredDevices();
+ }
+ }
+ };
+
@VisibleForTesting
final BluetoothGattServerCallback mServerCallback = new BluetoothGattServerCallback() {
@Override
@@ -551,6 +586,7 @@
mCharacteristics.get(CharId.CONTENT_CONTROL_ID)
.setValue(mCcid, BluetoothGattCharacteristic.FORMAT_UINT8, 0);
+ restoreCccValuesForStoredDevices();
setInitialCharacteristicValuesAndNotify();
initialStateRequest();
}
@@ -796,6 +832,15 @@
mMcpService = mcpService;
mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(),
"AdapterService shouldn't be null when creating MediaControlCattService");
+
+ IBluetoothManager mgr = BluetoothAdapter.getDefaultAdapter().getBluetoothManager();
+ if (mgr != null) {
+ try {
+ mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
protected boolean init(UUID scvUuid) {
@@ -987,16 +1032,77 @@
return mBluetoothGattServer.addService(mGattService);
}
+ private void removeUuidFromMetadata(ParcelUuid charUuid, BluetoothDevice device) {
+ List<ParcelUuid> uuidList;
+ byte[] gmcs_cccd = device.getMetadata(METADATA_GMCS_CCCD);
+
+ if ((gmcs_cccd == null) || (gmcs_cccd.length == 0)) {
+ uuidList = new ArrayList<ParcelUuid>();
+ } else {
+ uuidList = new ArrayList<>(Arrays.asList(Utils.byteArrayToUuid(gmcs_cccd)));
+
+ if (!uuidList.contains(charUuid)) {
+ Log.d(TAG, "Characteristic CCCD can't be removed (not cached): "
+ + charUuid.toString());
+ return;
+ }
+ }
+
+ uuidList.remove(charUuid);
+
+ if (!device.setMetadata(METADATA_GMCS_CCCD,
+ Utils.uuidsToByteArray(uuidList.toArray(new ParcelUuid[0])))) {
+ Log.e(TAG, "Can't set CCCD for GMCS characteristic UUID: " + charUuid.toString()
+ + ", (remove)");
+ }
+ }
+
+ private void addUuidToMetadata(ParcelUuid charUuid, BluetoothDevice device) {
+ List<ParcelUuid> uuidList;
+ byte[] gmcs_cccd = device.getMetadata(METADATA_GMCS_CCCD);
+
+ if ((gmcs_cccd == null) || (gmcs_cccd.length == 0)) {
+ uuidList = new ArrayList<ParcelUuid>();
+ } else {
+ uuidList = new ArrayList<>(Arrays.asList(Utils.byteArrayToUuid(gmcs_cccd)));
+
+ if (uuidList.contains(charUuid)) {
+ Log.d(TAG, "Characteristic CCCD already added: " + charUuid.toString());
+ return;
+ }
+ }
+
+ uuidList.add(charUuid);
+
+ if (!device.setMetadata(METADATA_GMCS_CCCD,
+ Utils.uuidsToByteArray(uuidList.toArray(new ParcelUuid[0])))) {
+ Log.e(TAG, "Can't set CCCD for GMCS characteristic UUID: " + charUuid.toString()
+ + ", (add)");
+ }
+ }
+
@VisibleForTesting
- void setCcc(BluetoothDevice device, UUID charUuid, int offset, byte[] value) {
+ void setCcc(BluetoothDevice device, UUID charUuid, int offset, byte[] value, boolean store) {
HashMap<UUID, Short> characteristicCcc = mCccDescriptorValues.get(device.getAddress());
if (characteristicCcc == null) {
characteristicCcc = new HashMap<>();
mCccDescriptorValues.put(device.getAddress(), characteristicCcc);
}
- characteristicCcc.put(
- charUuid, ByteBuffer.wrap(value).order(ByteOrder.LITTLE_ENDIAN).getShort());
+ characteristicCcc.put(charUuid,
+ ByteBuffer.wrap(value).order(ByteOrder.LITTLE_ENDIAN).getShort());
+
+ if (!store) {
+ return;
+ }
+
+ if (Arrays.equals(value, BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE)) {
+ addUuidToMetadata(new ParcelUuid(charUuid), device);
+ } else if (Arrays.equals(value, BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE)) {
+ removeUuidFromMetadata(new ParcelUuid(charUuid), device);
+ } else {
+ Log.e(TAG, "Not handled CCC value: " + Arrays.toString(value));
+ }
}
private byte[] getCccBytes(BluetoothDevice device, UUID charUuid) {
diff --git a/android/app/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java b/android/app/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java
index 301b39f..b5dd0f7 100644
--- a/android/app/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java
+++ b/android/app/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java
@@ -422,7 +422,8 @@
launchDevicePicker();
finish();
} catch (IllegalArgumentException exception) {
- showToast(exception.getMessage());
+ String message = exception.getMessage();
+ showToast(message != null ? message : "IllegalArgumentException");
finish();
}
}
diff --git a/android/app/src/com/android/bluetooth/opp/BluetoothOppService.java b/android/app/src/com/android/bluetooth/opp/BluetoothOppService.java
index 773d771..f9521b0 100644
--- a/android/app/src/com/android/bluetooth/opp/BluetoothOppService.java
+++ b/android/app/src/com/android/bluetooth/opp/BluetoothOppService.java
@@ -1115,6 +1115,11 @@
// Run in a background thread at boot.
private static void trimDatabase(ContentResolver contentResolver) {
+ if (contentResolver.acquireContentProviderClient(BluetoothShare.CONTENT_URI) == null) {
+ Log.w(TAG, "ContentProvider doesn't exist");
+ return;
+ }
+
// remove the invisible/unconfirmed inbound shares
int delNum = contentResolver.delete(BluetoothShare.CONTENT_URI, WHERE_INVISIBLE_UNCONFIRMED,
null);
diff --git a/android/app/src/com/android/bluetooth/opp/BluetoothOppTransfer.java b/android/app/src/com/android/bluetooth/opp/BluetoothOppTransfer.java
index 00fb596..eb10b23 100644
--- a/android/app/src/com/android/bluetooth/opp/BluetoothOppTransfer.java
+++ b/android/app/src/com/android/bluetooth/opp/BluetoothOppTransfer.java
@@ -92,23 +92,27 @@
private BluetoothAdapter mAdapter;
- private BluetoothDevice mDevice;
+ @VisibleForTesting
+ BluetoothDevice mDevice;
private BluetoothOppBatch mBatch;
private BluetoothOppObexSession mSession;
- private BluetoothOppShareInfo mCurrentShare;
+ @VisibleForTesting
+ BluetoothOppShareInfo mCurrentShare;
private ObexTransport mTransport;
private HandlerThread mHandlerThread;
- private EventHandler mSessionHandler;
+ @VisibleForTesting
+ EventHandler mSessionHandler;
private long mTimestamp;
- private class OppConnectionReceiver extends BroadcastReceiver {
+ @VisibleForTesting
+ class OppConnectionReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
@@ -139,8 +143,8 @@
// Remove the timeout message triggered earlier during Obex Put
mSessionHandler.removeMessages(BluetoothOppObexSession.MSG_CONNECT_TIMEOUT);
// Now reuse the same message to clean up the session.
- mSessionHandler.sendMessage(mSessionHandler.obtainMessage(
- BluetoothOppObexSession.MSG_CONNECT_TIMEOUT));
+ BluetoothMethodProxy.getInstance().handlerSendEmptyMessage(mSessionHandler,
+ BluetoothOppObexSession.MSG_CONNECT_TIMEOUT);
}
} catch (Exception e) {
e.printStackTrace();
@@ -160,7 +164,11 @@
Log.w(TAG, "OPP SDP search, target device is null, ignoring result");
return;
}
- if (!device.getIdentityAddress().equalsIgnoreCase(mDevice.getIdentityAddress())) {
+ String deviceIdentityAddress = device.getIdentityAddress();
+ String transferDeviceIdentityAddress = mDevice.getIdentityAddress();
+ if (deviceIdentityAddress == null || transferDeviceIdentityAddress == null
+ || !deviceIdentityAddress.equalsIgnoreCase(
+ transferDeviceIdentityAddress)) {
Log.w(TAG, " OPP SDP search for wrong device, ignoring!!");
return;
}
@@ -676,10 +684,12 @@
@VisibleForTesting
SocketConnectThread mConnectThread;
- private class SocketConnectThread extends Thread {
+ @VisibleForTesting
+ class SocketConnectThread extends Thread {
private final String mHost;
- private final BluetoothDevice mDevice;
+ @VisibleForTesting
+ final BluetoothDevice mDevice;
private final int mChannel;
@@ -695,7 +705,8 @@
private boolean mSdpInitiated = false;
- private boolean mIsInterrupted = false;
+ @VisibleForTesting
+ boolean mIsInterrupted = false;
/* create a Rfcomm/L2CAP Socket */
SocketConnectThread(BluetoothDevice device, boolean retry) {
@@ -863,7 +874,8 @@
Log.e(TAG, "Error when close socket");
}
}
- mSessionHandler.obtainMessage(TRANSPORT_ERROR).sendToTarget();
+ BluetoothMethodProxy.getInstance().handlerSendEmptyMessage(mSessionHandler,
+ TRANSPORT_ERROR);
return;
}
diff --git a/android/app/src/com/android/bluetooth/tbs/BluetoothGattServerProxy.java b/android/app/src/com/android/bluetooth/tbs/BluetoothGattServerProxy.java
index a68af61..59688a6 100644
--- a/android/app/src/com/android/bluetooth/tbs/BluetoothGattServerProxy.java
+++ b/android/app/src/com/android/bluetooth/tbs/BluetoothGattServerProxy.java
@@ -27,6 +27,7 @@
import android.content.Context;
import java.util.List;
+import java.util.UUID;
/**
* A proxy class that facilitates testing of the TbsService class.
@@ -63,6 +64,21 @@
return mBluetoothGattServer.addService(service);
}
+ /**
+ * A proxy that Returns a {@link BluetoothGattService} from the list of services offered
+ * by this device.
+ *
+ * <p>If multiple instances of the same service (as identified by UUID)
+ * exist, the first instance of the service is returned.
+ *
+ * @param uuid UUID of the requested service
+ * @return BluetoothGattService if supported, or null if the requested service is not offered by
+ * this device.
+ */
+ public BluetoothGattService getService(UUID uuid) {
+ return mBluetoothGattServer.getService(uuid);
+ }
+
public boolean sendResponse(BluetoothDevice device, int requestId, int status, int offset,
byte[] value) {
return mBluetoothGattServer.sendResponse(device, requestId, status, offset, value);
diff --git a/android/app/src/com/android/bluetooth/tbs/TbsGatt.java b/android/app/src/com/android/bluetooth/tbs/TbsGatt.java
index 7c3f9fc..4c1833a 100644
--- a/android/app/src/com/android/bluetooth/tbs/TbsGatt.java
+++ b/android/app/src/com/android/bluetooth/tbs/TbsGatt.java
@@ -17,17 +17,27 @@
package com.android.bluetooth.tbs;
+import static android.bluetooth.BluetoothDevice.METADATA_GTBS_CCCD;
+
+import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattServerCallback;
import android.bluetooth.BluetoothGattService;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.IBluetoothManager;
+import android.bluetooth.IBluetoothStateChangeCallback;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
+import android.os.ParcelUuid;
+import android.os.RemoteException;
import android.util.Log;
+import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.Utils;
import com.android.internal.annotations.VisibleForTesting;
import java.io.ByteArrayOutputStream;
@@ -35,6 +45,7 @@
import java.util.Arrays;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.UUID;
public class TbsGatt {
@@ -131,9 +142,11 @@
private final GattCharacteristic mTerminationReasonCharacteristic;
private final GattCharacteristic mIncomingCallCharacteristic;
private final GattCharacteristic mCallFriendlyNameCharacteristic;
+ private List<BluetoothDevice> mSubscribers = new ArrayList<>();
private BluetoothGattServerProxy mBluetoothGattServer;
private Handler mHandler;
private Callback mCallback;
+ private AdapterService mAdapterService;
public static abstract class Callback {
@@ -144,6 +157,17 @@
}
TbsGatt(Context context) {
+ mAdapterService = Objects.requireNonNull(AdapterService.getAdapterService(),
+ "AdapterService shouldn't be null when creating MediaControlCattService");
+ IBluetoothManager mgr = BluetoothAdapter.getDefaultAdapter().getBluetoothManager();
+ if (mgr != null) {
+ try {
+ mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
mContext = context;
mBearerProviderNameCharacteristic = new GattCharacteristic(UUID_BEARER_PROVIDER_NAME,
BluetoothGattCharacteristic.PROPERTY_READ
@@ -252,11 +276,55 @@
return mContext;
}
+ private void removeUuidFromMetadata(ParcelUuid charUuid, BluetoothDevice device) {
+ List<ParcelUuid> uuidList;
+ byte[] gtbs_cccd = device.getMetadata(METADATA_GTBS_CCCD);
+
+ if ((gtbs_cccd == null) || (gtbs_cccd.length == 0)) {
+ uuidList = new ArrayList<ParcelUuid>();
+ } else {
+ uuidList = new ArrayList<>(Arrays.asList(Utils.byteArrayToUuid(gtbs_cccd)));
+
+ if (!uuidList.contains(charUuid)) {
+ Log.d(TAG, "Characteristic CCCD can't be removed (not cached): "
+ + charUuid.toString());
+ return;
+ }
+ }
+
+ uuidList.remove(charUuid);
+
+ if (!device.setMetadata(METADATA_GTBS_CCCD,
+ Utils.uuidsToByteArray(uuidList.toArray(new ParcelUuid[0])))) {
+ Log.e(TAG, "Can't set CCCD for GTBS characteristic UUID: " + charUuid + ", (remove)");
+ }
+ }
+
+ private void addUuidToMetadata(ParcelUuid charUuid, BluetoothDevice device) {
+ List<ParcelUuid> uuidList;
+ byte[] gtbs_cccd = device.getMetadata(METADATA_GTBS_CCCD);
+
+ if ((gtbs_cccd == null) || (gtbs_cccd.length == 0)) {
+ uuidList = new ArrayList<ParcelUuid>();
+ } else {
+ uuidList = new ArrayList<>(Arrays.asList(Utils.byteArrayToUuid(gtbs_cccd)));
+
+ if (uuidList.contains(charUuid)) {
+ Log.d(TAG, "Characteristic CCCD already add: " + charUuid.toString());
+ return;
+ }
+ }
+
+ uuidList.add(charUuid);
+
+ if (!device.setMetadata(METADATA_GTBS_CCCD,
+ Utils.uuidsToByteArray(uuidList.toArray(new ParcelUuid[0])))) {
+ Log.e(TAG, "Can't set CCCD for GTBS characteristic UUID: " + charUuid + ", (add)");
+ }
+ }
+
/** Class that handles GATT characteristic notifications */
private class BluetoothGattCharacteristicNotifier {
-
- private List<BluetoothDevice> mSubscribers = new ArrayList<>();
-
public int setSubscriptionConfiguration(BluetoothDevice device, byte[] configuration) {
if (Arrays.equals(configuration, BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE)) {
mSubscribers.remove(device);
@@ -455,6 +523,14 @@
return BluetoothGatt.GATT_FAILURE;
}
+ if (Arrays.equals(value, BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE)) {
+ addUuidToMetadata(new ParcelUuid(characteristic.getUuid()), device);
+ } else if (Arrays.equals(value, BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE)) {
+ removeUuidFromMetadata(new ParcelUuid(characteristic.getUuid()), device);
+ } else {
+ Log.e(TAG, "Not handled CCC value: " + Arrays.toString(value));
+ }
+
return characteristic.setSubscriptionConfiguration(device, value);
}
}
@@ -659,13 +735,56 @@
return UUID.fromString(UUID_PREFIX + uuid16 + UUID_SUFFIX);
}
+ private void restoreCccValuesForStoredDevices() {
+ BluetoothGattService gattService = mBluetoothGattServer.getService(UUID_GTBS);
+
+ for (BluetoothDevice device : mAdapterService.getBondedDevices()) {
+ byte[] gtbs_cccd = device.getMetadata(METADATA_GTBS_CCCD);
+
+ if ((gtbs_cccd == null) || (gtbs_cccd.length == 0)) {
+ return;
+ }
+
+ List<ParcelUuid> uuidList = Arrays.asList(Utils.byteArrayToUuid(gtbs_cccd));
+
+ /* Restore CCCD values for device */
+ for (ParcelUuid uuid : uuidList) {
+ BluetoothGattCharacteristic characteristic =
+ gattService.getCharacteristic(uuid.getUuid());
+ if (characteristic == null) {
+ Log.e(TAG, "Invalid UUID stored in metadata: " + uuid.toString());
+ continue;
+ }
+
+ BluetoothGattDescriptor descriptor =
+ characteristic.getDescriptor(UUID_CLIENT_CHARACTERISTIC_CONFIGURATION);
+ if (descriptor == null) {
+ Log.e(TAG, "Invalid characteristic, does not include CCCD");
+ continue;
+ }
+
+ descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
+ mSubscribers.add(device);
+ }
+ }
+ }
+
+ private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
+ new IBluetoothStateChangeCallback.Stub() {
+ public void onBluetoothStateChange(boolean up) {
+ if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
+ if (up) {
+ restoreCccValuesForStoredDevices();
+ }
+ }
+ };
+
/**
* Callback to handle incoming requests to the GATT server. All read/write requests for
* characteristics and descriptors are handled here.
*/
@VisibleForTesting
final BluetoothGattServerCallback mGattServerCallback = new BluetoothGattServerCallback() {
-
@Override
public void onServiceAdded(int status, BluetoothGattService service) {
if (DBG) {
@@ -674,6 +793,8 @@
if (mCallback != null) {
mCallback.onServiceAdded(status == BluetoothGatt.GATT_SUCCESS);
}
+
+ restoreCccValuesForStoredDevices();
}
@Override
diff --git a/android/app/src/com/android/bluetooth/telephony/BluetoothInCallService.java b/android/app/src/com/android/bluetooth/telephony/BluetoothInCallService.java
index 46eb260..7cc5bac 100644
--- a/android/app/src/com/android/bluetooth/telephony/BluetoothInCallService.java
+++ b/android/app/src/com/android/bluetooth/telephony/BluetoothInCallService.java
@@ -708,7 +708,7 @@
// We do, however want to send conferences that have no children to the bluetooth
// device (e.g. IMS Conference).
boolean isConferenceWithNoChildren = isConferenceWithNoChildren(call);
- Log.i(TAG, "sendListOfCalls isConferenceWithNoChildren " + isConferenceWithNoChildren
+ Log.i(TAG, "sendListOfCalls isConferenceWithNoChildren " + isConferenceWithNoChildren
+ ", call.getChildrenIds() size " + call.getChildrenIds().size());
if (!call.isConference() || isConferenceWithNoChildren) {
sendClccForCall(call, shouldLog);
@@ -1479,7 +1479,9 @@
mBluetoothLeCallControl.currentCallsList(tbsCalls);
}
- private final BluetoothLeCallControl.Callback mBluetoothLeCallControlCallback = new BluetoothLeCallControl.Callback() {
+ @VisibleForTesting
+ final BluetoothLeCallControl.Callback mBluetoothLeCallControlCallback =
+ new BluetoothLeCallControl.Callback() {
@Override
public void onAcceptCall(int requestId, UUID callId) {
diff --git a/android/app/tests/unit/AndroidTest.xml b/android/app/tests/unit/AndroidTest.xml
index 8414185..4358ed8 100644
--- a/android/app/tests/unit/AndroidTest.xml
+++ b/android/app/tests/unit/AndroidTest.xml
@@ -64,9 +64,10 @@
<option name="hidden-api-checks" value="false"/>
</test>
- <!-- Only run Cts Tests in MTS if the Bluetooth Mainline module is installed. -->
+ <!-- Only run if the Bluetooth Mainline module is installed. -->
<object type="module_controller"
class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+ <option name="enable" value="true" />
<option name="mainline-module-package-name" value="com.android.btservices" />
</object>
</configuration>
diff --git a/android/app/tests/unit/GoogleAndroidTest.xml b/android/app/tests/unit/GoogleAndroidTest.xml
index 24fa605..8300f6c 100644
--- a/android/app/tests/unit/GoogleAndroidTest.xml
+++ b/android/app/tests/unit/GoogleAndroidTest.xml
@@ -64,9 +64,10 @@
<option name="hidden-api-checks" value="false"/>
</test>
- <!-- Only run Cts Tests in MTS if the Bluetooth Mainline module is installed. -->
+ <!-- Only run if the Google Bluetooth Mainline module is installed. -->
<object type="module_controller"
class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+ <option name="enable" value="true" />
<option name="mainline-module-package-name" value="com.google.android.btservices" />
</object>
</configuration>
diff --git a/android/app/tests/unit/src/com/android/bluetooth/SignedLongLongTest.java b/android/app/tests/unit/src/com/android/bluetooth/SignedLongLongTest.java
new file mode 100644
index 0000000..0a4b682
--- /dev/null
+++ b/android/app/tests/unit/src/com/android/bluetooth/SignedLongLongTest.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.bluetooth;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test for SignedLongLong.java
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SignedLongLongTest {
+
+ @Test
+ public void compareTo_sameValue_returnsZero() {
+ long mostSigBits = 1352;
+ long leastSigBits = 53423;
+
+ SignedLongLong value = new SignedLongLong(mostSigBits, leastSigBits);
+ SignedLongLong sameValue = new SignedLongLong(mostSigBits, leastSigBits);
+
+ assertThat(value.compareTo(sameValue)).isEqualTo(0);
+ }
+
+ @Test
+ public void compareTo_biggerLeastSigBits_returnsMinusOne() {
+ long commonMostSigBits = 12345;
+ long leastSigBits = 1;
+ SignedLongLong value = new SignedLongLong(leastSigBits, commonMostSigBits);
+
+ long biggerLeastSigBits = 2;
+ SignedLongLong biggerValue = new SignedLongLong(biggerLeastSigBits, commonMostSigBits);
+
+ assertThat(value.compareTo(biggerValue)).isEqualTo(-1);
+ }
+
+ @Test
+ public void compareTo_smallerLeastSigBits_returnsOne() {
+ long commonMostSigBits = 12345;
+ long leastSigBits = 2;
+ SignedLongLong value = new SignedLongLong(leastSigBits, commonMostSigBits);
+
+ long smallerLeastSigBits = 1;
+ SignedLongLong smallerValue = new SignedLongLong(smallerLeastSigBits, commonMostSigBits);
+
+ assertThat(value.compareTo(smallerValue)).isEqualTo(1);
+ }
+
+ @Test
+ public void compareTo_biggerMostSigBits_returnsMinusOne() {
+ long commonLeastSigBits = 12345;
+ long mostSigBits = 1;
+ SignedLongLong value = new SignedLongLong(commonLeastSigBits, mostSigBits);
+
+ long biggerMostSigBits = 2;
+ SignedLongLong biggerValue = new SignedLongLong(commonLeastSigBits, biggerMostSigBits);
+
+ assertThat(value.compareTo(biggerValue)).isEqualTo(-1);
+ }
+
+ @Test
+ public void compareTo_smallerMostSigBits_returnsOne() {
+ long commonLeastSigBits = 12345;
+ long mostSigBits = 2;
+ SignedLongLong value = new SignedLongLong(commonLeastSigBits, mostSigBits);
+
+ long smallerMostSigBits = 1;
+ SignedLongLong smallerValue = new SignedLongLong(commonLeastSigBits, smallerMostSigBits);
+
+ assertThat(value.compareTo(smallerValue)).isEqualTo(1);
+ }
+
+ @Test
+ public void toString_RepresentedAsHexValues() {
+ SignedLongLong value = new SignedLongLong(2, 11);
+
+ assertThat(value.toString()).isEqualTo("B0000000000000002");
+ }
+
+ @SuppressWarnings("EqualsIncompatibleType")
+ @Test
+ public void equals_variousCases() {
+ SignedLongLong value = new SignedLongLong(1, 2);
+
+ assertThat(value.equals(value)).isTrue();
+ assertThat(value.equals(null)).isFalse();
+ assertThat(value.equals("a random string")).isFalse();
+ assertThat(value.equals(new SignedLongLong(1, 1))).isFalse();
+ assertThat(value.equals(new SignedLongLong(2, 2))).isFalse();
+ assertThat(value.equals(new SignedLongLong(1, 2))).isTrue();
+ }
+
+ @Test
+ public void fromString_whenStringIsNull_throwsNPE() {
+ assertThrows(NullPointerException.class, () -> SignedLongLong.fromString(null));
+ }
+
+ @Test
+ public void fromString_whenLengthIsInvalid_throwsNumberFormatException() {
+ assertThrows(NumberFormatException.class, () -> SignedLongLong.fromString(""));
+ }
+
+ @Test
+ public void fromString_whenLengthIsNotGreaterThan16() throws Exception {
+ String strValue = "1";
+
+ assertThat(SignedLongLong.fromString(strValue))
+ .isEqualTo(new SignedLongLong(1, 0));
+ }
+
+ @Test
+ public void fromString_whenLengthIsGreaterThan16() throws Exception {
+ String strValue = "B0000000000000002";
+
+ assertThat(SignedLongLong.fromString(strValue))
+ .isEqualTo(new SignedLongLong(2, 11));
+ }
+}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/UtilsTest.java b/android/app/tests/unit/src/com/android/bluetooth/UtilsTest.java
index 6a63eb9..20e830c 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/UtilsTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/UtilsTest.java
@@ -17,12 +17,16 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.verify;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.location.LocationManager;
+import android.os.Build;
import android.os.ParcelUuid;
import android.os.UserHandle;
@@ -36,6 +40,11 @@
import org.junit.runner.RunWith;
import org.mockito.Mockito;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.UUID;
@@ -168,4 +177,76 @@
String loggableAddress = "xx:xx:xx:xx:" + device.getAddress().substring(12);
assertThat(Utils.getLoggableAddress(device)).isEqualTo(loggableAddress);
}
+
+ @Test
+ public void checkCallerIsSystemMethods_doesNotCrash() {
+ Context context = InstrumentationRegistry.getTargetContext();
+ String tag = "test_tag";
+
+ Utils.checkCallerIsSystemOrActiveOrManagedUser(context, tag);
+ Utils.checkCallerIsSystemOrActiveOrManagedUser(null, tag);
+ Utils.checkCallerIsSystemOrActiveUser(tag);
+ }
+
+ @Test
+ public void testCopyStream() throws Exception {
+ byte[] data = new byte[] {1, 2, 3, 4, 5, 6, 7, 8};
+ ByteArrayInputStream in = new ByteArrayInputStream(data);
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ int bufferSize = 4;
+
+ Utils.copyStream(in, out, bufferSize);
+
+ assertThat(out.toByteArray()).isEqualTo(data);
+ }
+
+ @Test
+ public void debugGetAdapterStateString() {
+ assertThat(Utils.debugGetAdapterStateString(BluetoothAdapter.STATE_OFF))
+ .isEqualTo("STATE_OFF");
+ assertThat(Utils.debugGetAdapterStateString(BluetoothAdapter.STATE_ON))
+ .isEqualTo("STATE_ON");
+ assertThat(Utils.debugGetAdapterStateString(BluetoothAdapter.STATE_TURNING_ON))
+ .isEqualTo("STATE_TURNING_ON");
+ assertThat(Utils.debugGetAdapterStateString(BluetoothAdapter.STATE_TURNING_OFF))
+ .isEqualTo("STATE_TURNING_OFF");
+ assertThat(Utils.debugGetAdapterStateString(-124))
+ .isEqualTo("UNKNOWN");
+ }
+
+ @Test
+ public void ellipsize() {
+ if (!Build.TYPE.equals("user")) {
+ // Only ellipsize release builds
+ String input = "a_long_string";
+ assertThat(Utils.ellipsize(input)).isEqualTo(input);
+ return;
+ }
+
+ assertThat(Utils.ellipsize("ab")).isEqualTo("ab");
+ assertThat(Utils.ellipsize("abc")).isEqualTo("a⋯c");
+ assertThat(Utils.ellipsize(null)).isEqualTo(null);
+ }
+
+ @Test
+ public void safeCloseStream_inputStream_doesNotCrash() throws Exception {
+ InputStream is = mock(InputStream.class);
+ Utils.safeCloseStream(is);
+ verify(is).close();
+
+ Mockito.clearInvocations(is);
+ doThrow(new IOException()).when(is).close();
+ Utils.safeCloseStream(is);
+ }
+
+ @Test
+ public void safeCloseStream_outputStream_doesNotCrash() throws Exception {
+ OutputStream os = mock(OutputStream.class);
+ Utils.safeCloseStream(os);
+ verify(os).close();
+
+ Mockito.clearInvocations(os);
+ doThrow(new IOException()).when(os).close();
+ Utils.safeCloseStream(os);
+ }
}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/audio_util/GPMWrapperTest.java b/android/app/tests/unit/src/com/android/bluetooth/audio_util/GPMWrapperTest.java
new file mode 100644
index 0000000..7f03b37
--- /dev/null
+++ b/android/app/tests/unit/src/com/android/bluetooth/audio_util/GPMWrapperTest.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.bluetooth.audio_util;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.media.MediaDescription;
+import android.media.MediaMetadata;
+import android.media.session.MediaSession;
+import android.media.session.PlaybackState;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class GPMWrapperTest {
+
+ private Context mContext;
+ private MediaController mMediaController;
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mMediaController = mock(MediaController.class);
+ }
+
+ @Test
+ public void isMetadataSynced_whenQueueIsNull_returnsFalse() {
+ when(mMediaController.getQueue()).thenReturn(null);
+
+ GPMWrapper wrapper = new GPMWrapper(mContext, mMediaController, null);
+
+ assertThat(wrapper.isMetadataSynced()).isFalse();
+ }
+
+ @Test
+ public void isMetadataSynced_whenOutOfSync_returnsFalse() {
+ long activeQueueItemId = 3;
+ PlaybackState state = new PlaybackState.Builder()
+ .setActiveQueueItemId(activeQueueItemId).build();
+ when(mMediaController.getPlaybackState()).thenReturn(state);
+
+ List<MediaSession.QueueItem> queue = new ArrayList<>();
+ MediaDescription description = new MediaDescription.Builder()
+ .setTitle("Title from queue item")
+ .build();
+ MediaSession.QueueItem queueItem = new MediaSession.QueueItem(
+ description, activeQueueItemId);
+ queue.add(queueItem);
+ when(mMediaController.getQueue()).thenReturn(queue);
+
+ MediaMetadata metadata = new MediaMetadata.Builder()
+ .putString(MediaMetadata.METADATA_KEY_TITLE,
+ "Different Title from MediaMetadata")
+ .build();
+ when(mMediaController.getMetadata()).thenReturn(metadata);
+
+ GPMWrapper wrapper = new GPMWrapper(mContext, mMediaController, null);
+
+ assertThat(wrapper.isMetadataSynced()).isFalse();
+ }
+
+ @Test
+ public void isMetadataSynced_whenSynced_returnsTrue() {
+ String title = "test_title";
+
+ long activeQueueItemId = 3;
+ PlaybackState state = new PlaybackState.Builder()
+ .setActiveQueueItemId(activeQueueItemId).build();
+ when(mMediaController.getPlaybackState()).thenReturn(state);
+
+ List<MediaSession.QueueItem> queue = new ArrayList<>();
+ MediaDescription description = new MediaDescription.Builder()
+ .setTitle(title)
+ .build();
+ MediaSession.QueueItem queueItem = new MediaSession.QueueItem(
+ description, activeQueueItemId);
+ queue.add(queueItem);
+ when(mMediaController.getQueue()).thenReturn(queue);
+
+ MediaMetadata metadata = new MediaMetadata.Builder()
+ .putString(MediaMetadata.METADATA_KEY_TITLE, title)
+ .build();
+ when(mMediaController.getMetadata()).thenReturn(metadata);
+
+ GPMWrapper wrapper = new GPMWrapper(mContext, mMediaController, null);
+
+ assertThat(wrapper.isMetadataSynced()).isTrue();
+ }
+}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/audio_util/MediaPlayerWrapperTest.java b/android/app/tests/unit/src/com/android/bluetooth/audio_util/MediaPlayerWrapperTest.java
index 16ba201..2940d00 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/audio_util/MediaPlayerWrapperTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/audio_util/MediaPlayerWrapperTest.java
@@ -16,6 +16,8 @@
package com.android.bluetooth.audio_util;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.Mockito.*;
import android.content.Context;
@@ -717,4 +719,144 @@
Assert.assertFalse(wrapper.getTimeoutHandler().hasMessages(MSG_TIMEOUT));
verify(mFailHandler, never()).onTerribleFailure(any(), any(), anyBoolean());
}
+
+ @Test
+ public void pauseCurrent() {
+ MediaController.TransportControls transportControls
+ = mock(MediaController.TransportControls.class);
+ when(mMockController.getTransportControls()).thenReturn(transportControls);
+ MediaPlayerWrapper wrapper =
+ MediaPlayerWrapperFactory.wrap(mMockContext, mMockController, mThread.getLooper());
+
+ wrapper.pauseCurrent();
+
+ verify(transportControls).pause();
+ }
+
+ @Test
+ public void playCurrent() {
+ MediaController.TransportControls transportControls
+ = mock(MediaController.TransportControls.class);
+ when(mMockController.getTransportControls()).thenReturn(transportControls);
+ MediaPlayerWrapper wrapper =
+ MediaPlayerWrapperFactory.wrap(mMockContext, mMockController, mThread.getLooper());
+
+ wrapper.playCurrent();
+
+ verify(transportControls).play();
+ }
+
+ @Test
+ public void playItemFromQueue() {
+ MediaController.TransportControls transportControls
+ = mock(MediaController.TransportControls.class);
+ when(mMockController.getTransportControls()).thenReturn(transportControls);
+ when(mMockController.getQueue()).thenReturn(new ArrayList<>());
+ MediaPlayerWrapper wrapper =
+ MediaPlayerWrapperFactory.wrap(mMockContext, mMockController, mThread.getLooper());
+
+ long queueItemId = 4;
+ wrapper.playItemFromQueue(queueItemId);
+
+ verify(transportControls).skipToQueueItem(queueItemId);
+ }
+
+ @Test
+ public void rewind() {
+ MediaController.TransportControls transportControls
+ = mock(MediaController.TransportControls.class);
+ when(mMockController.getTransportControls()).thenReturn(transportControls);
+ MediaPlayerWrapper wrapper =
+ MediaPlayerWrapperFactory.wrap(mMockContext, mMockController, mThread.getLooper());
+
+ wrapper.rewind();
+
+ verify(transportControls).rewind();
+ }
+
+ @Test
+ public void seekTo() {
+ MediaController.TransportControls transportControls
+ = mock(MediaController.TransportControls.class);
+ when(mMockController.getTransportControls()).thenReturn(transportControls);
+ MediaPlayerWrapper wrapper =
+ MediaPlayerWrapperFactory.wrap(mMockContext, mMockController, mThread.getLooper());
+
+ long position = 50;
+ wrapper.seekTo(position);
+
+ verify(transportControls).seekTo(position);
+ }
+
+ @Test
+ public void setPlaybackSpeed() {
+ MediaController.TransportControls transportControls
+ = mock(MediaController.TransportControls.class);
+ when(mMockController.getTransportControls()).thenReturn(transportControls);
+ MediaPlayerWrapper wrapper =
+ MediaPlayerWrapperFactory.wrap(mMockContext, mMockController, mThread.getLooper());
+
+ float speed = 2.0f;
+ wrapper.setPlaybackSpeed(speed);
+
+ verify(transportControls).setPlaybackSpeed(speed);
+ }
+
+ @Test
+ public void skipToNext() {
+ MediaController.TransportControls transportControls
+ = mock(MediaController.TransportControls.class);
+ when(mMockController.getTransportControls()).thenReturn(transportControls);
+ MediaPlayerWrapper wrapper =
+ MediaPlayerWrapperFactory.wrap(mMockContext, mMockController, mThread.getLooper());
+
+ wrapper.skipToNext();
+
+ verify(transportControls).skipToNext();
+ }
+
+ @Test
+ public void skipToPrevious() {
+ MediaController.TransportControls transportControls
+ = mock(MediaController.TransportControls.class);
+ when(mMockController.getTransportControls()).thenReturn(transportControls);
+ MediaPlayerWrapper wrapper =
+ MediaPlayerWrapperFactory.wrap(mMockContext, mMockController, mThread.getLooper());
+
+ wrapper.skipToPrevious();
+
+ verify(transportControls).skipToPrevious();
+ }
+
+ @Test
+ public void stopCurrent() {
+ MediaController.TransportControls transportControls
+ = mock(MediaController.TransportControls.class);
+ when(mMockController.getTransportControls()).thenReturn(transportControls);
+ MediaPlayerWrapper wrapper =
+ MediaPlayerWrapperFactory.wrap(mMockContext, mMockController, mThread.getLooper());
+
+ wrapper.stopCurrent();
+
+ verify(transportControls).stop();
+ }
+
+ @Test
+ public void toggleRepeat_andToggleShuffle_doesNotCrash() {
+ MediaPlayerWrapper wrapper =
+ MediaPlayerWrapperFactory.wrap(mMockContext, mMockController, mThread.getLooper());
+
+ wrapper.toggleRepeat(true);
+ wrapper.toggleRepeat(false);
+ wrapper.toggleShuffle(true);
+ wrapper.toggleShuffle(false);
+ }
+
+ @Test
+ public void toString_doesNotCrash() {
+ MediaPlayerWrapper wrapper =
+ MediaPlayerWrapperFactory.wrap(mMockContext, mMockController, mThread.getLooper());
+
+ assertThat(wrapper.toString()).isNotEmpty();
+ }
}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/avrcp/AvrcpVolumeManagerTest.java b/android/app/tests/unit/src/com/android/bluetooth/avrcp/AvrcpVolumeManagerTest.java
new file mode 100644
index 0000000..dd57998
--- /dev/null
+++ b/android/app/tests/unit/src/com/android/bluetooth/avrcp/AvrcpVolumeManagerTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.avrcp;
+
+import static com.android.bluetooth.avrcp.AvrcpVolumeManager.AVRCP_MAX_VOL;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.media.AudioManager;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class AvrcpVolumeManagerTest {
+ private static final String REMOTE_DEVICE_ADDRESS = "00:01:02:03:04:05";
+ private static final int TEST_DEVICE_MAX_VOUME = 25;
+
+ @Mock
+ AvrcpNativeInterface mNativeInterface;
+
+ @Mock
+ AudioManager mAudioManager;
+
+ Context mContext;
+ BluetoothDevice mRemoteDevice;
+ AvrcpVolumeManager mAvrcpVolumeManager;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mContext = InstrumentationRegistry.getTargetContext();
+ when(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
+ .thenReturn(TEST_DEVICE_MAX_VOUME);
+ mRemoteDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(REMOTE_DEVICE_ADDRESS);
+ mAvrcpVolumeManager = new AvrcpVolumeManager(mContext, mAudioManager, mNativeInterface);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mAvrcpVolumeManager.removeStoredVolumeForDevice(mRemoteDevice);
+ }
+
+ @Test
+ public void avrcpToSystemVolume() {
+ assertThat(AvrcpVolumeManager.avrcpToSystemVolume(0)).isEqualTo(0);
+ assertThat(AvrcpVolumeManager.avrcpToSystemVolume(AVRCP_MAX_VOL))
+ .isEqualTo(TEST_DEVICE_MAX_VOUME);
+ }
+
+ @Test
+ public void dump() {
+ StringBuilder sb = new StringBuilder();
+ mAvrcpVolumeManager.dump(sb);
+
+ assertThat(sb.toString()).isNotEmpty();
+ }
+
+ @Test
+ public void sendVolumeChanged() {
+ mAvrcpVolumeManager.sendVolumeChanged(mRemoteDevice, TEST_DEVICE_MAX_VOUME);
+
+ verify(mNativeInterface).sendVolumeChanged(REMOTE_DEVICE_ADDRESS, AVRCP_MAX_VOL);
+ }
+
+ @Test
+ public void setVolume() {
+ mAvrcpVolumeManager.setVolume(mRemoteDevice, AVRCP_MAX_VOL);
+
+ verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC),
+ eq(TEST_DEVICE_MAX_VOUME), anyInt());
+ }
+}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/bas/BatteryServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/bas/BatteryServiceTest.java
index 8c5ee71..e4a706d 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/bas/BatteryServiceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/bas/BatteryServiceTest.java
@@ -242,6 +242,31 @@
Assert.assertFalse("Connect expected to fail", mService.connect(mDevice));
}
+ @Test
+ public void getConnectionState_whenNoDevicesAreConnected_returnsDisconnectedState() {
+ Assert.assertEquals(mService.getConnectionState(mDevice),
+ BluetoothProfile.STATE_DISCONNECTED);
+ }
+
+ @Test
+ public void getDevices_whenNoDevicesAreConnected_returnsEmptyList() {
+ Assert.assertTrue(mService.getDevices().isEmpty());
+ }
+
+ @Test
+ public void getDevicesMatchingConnectionStates() {
+ when(mAdapterService.getBondedDevices()).thenReturn(new BluetoothDevice[] {mDevice});
+ int states[] = new int[] {BluetoothProfile.STATE_DISCONNECTED};
+
+ Assert.assertTrue(mService.getDevicesMatchingConnectionStates(states).contains(mDevice));
+ }
+
+ @Test
+ public void setConnectionPolicy() {
+ Assert.assertTrue(mService.setConnectionPolicy(
+ mDevice, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN));
+ }
+
/**
* Helper function to test okToConnect() method
*
diff --git a/android/app/tests/unit/src/com/android/bluetooth/bass_client/PeriodicAdvertisementResultTest.java b/android/app/tests/unit/src/com/android/bluetooth/bass_client/PeriodicAdvertisementResultTest.java
new file mode 100644
index 0000000..b80c949
--- /dev/null
+++ b/android/app/tests/unit/src/com/android/bluetooth/bass_client/PeriodicAdvertisementResultTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.bass_client;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class PeriodicAdvertisementResultTest {
+ private static final String REMOTE_DEVICE_ADDRESS = "00:01:02:03:04:05";
+
+ BluetoothDevice mDevice;
+
+ @Before
+ public void setUp() {
+ mDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(REMOTE_DEVICE_ADDRESS);
+ }
+
+ @Test
+ public void constructor() {
+ int addressType = 1;
+ int syncHandle = 2;
+ int advSid = 3;
+ int paInterval = 4;
+ int broadcastId = 5;
+ PeriodicAdvertisementResult result = new PeriodicAdvertisementResult(
+ mDevice, addressType, syncHandle, advSid, paInterval, broadcastId);
+
+ assertThat(result.getAddressType()).isEqualTo(addressType);
+ assertThat(result.getSyncHandle()).isEqualTo(syncHandle);
+ assertThat(result.getAdvSid()).isEqualTo(advSid);
+ assertThat(result.getAdvInterval()).isEqualTo(paInterval);
+ assertThat(result.getBroadcastId()).isEqualTo(broadcastId);
+ }
+
+ @Test
+ public void updateMethods() {
+ int addressType = 1;
+ int syncHandle = 2;
+ int advSid = 3;
+ int paInterval = 4;
+ int broadcastId = 5;
+ PeriodicAdvertisementResult result = new PeriodicAdvertisementResult(
+ mDevice, addressType, syncHandle, advSid, paInterval, broadcastId);
+
+ int newAddressType = 6;
+ result.updateAddressType(newAddressType);
+ assertThat(result.getAddressType()).isEqualTo(newAddressType);
+
+ int newSyncHandle = 7;
+ result.updateSyncHandle(newSyncHandle);
+ assertThat(result.getSyncHandle()).isEqualTo(newSyncHandle);
+
+ int newAdvSid = 8;
+ result.updateAdvSid(newAdvSid);
+ assertThat(result.getAdvSid()).isEqualTo(newAdvSid);
+
+ int newAdvInterval = 9;
+ result.updateAdvInterval(newAdvInterval);
+ assertThat(result.getAdvInterval()).isEqualTo(newAdvInterval);
+
+ int newBroadcastId = 10;
+ result.updateBroadcastId(newBroadcastId);
+ assertThat(result.getBroadcastId()).isEqualTo(newBroadcastId);
+ }
+
+ @Test
+ public void print_doesNotCrash() {
+ int addressType = 1;
+ int syncHandle = 2;
+ int advSid = 3;
+ int paInterval = 4;
+ int broadcastId = 5;
+ PeriodicAdvertisementResult result = new PeriodicAdvertisementResult(
+ mDevice, addressType, syncHandle, advSid, paInterval, broadcastId);
+
+ result.print();
+ }
+}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/btservice/ActiveDeviceManagerTest.java b/android/app/tests/unit/src/com/android/bluetooth/btservice/ActiveDeviceManagerTest.java
index 0854962..c6ed3c1 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/btservice/ActiveDeviceManagerTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/btservice/ActiveDeviceManagerTest.java
@@ -32,6 +32,7 @@
import android.bluetooth.BluetoothHearingAid;
import android.bluetooth.BluetoothLeAudio;
import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothSinkAudioPolicy;
import android.content.Context;
import android.content.Intent;
import android.media.AudioManager;
@@ -123,6 +124,8 @@
mMostRecentDevice = null;
when(mA2dpService.setActiveDevice(any())).thenReturn(true);
+ when(mHeadsetService.getHfpCallAudioPolicy(any())).thenReturn(
+ new BluetoothSinkAudioPolicy.Builder().build());
when(mHeadsetService.setActiveDevice(any())).thenReturn(true);
when(mHearingAidService.setActiveDevice(any())).thenReturn(true);
when(mLeAudioService.setActiveDevice(any())).thenReturn(true);
@@ -310,6 +313,24 @@
}
/**
+ * A headset device with connecting audio policy set to NOT ALLOWED.
+ */
+ @Test
+ public void notAllowedConnectingPolicyHeadsetConnected_noSetActiveDevice() {
+ // setting connecting policy to NOT ALLOWED
+ when(mHeadsetService.getHfpCallAudioPolicy(mHeadsetDevice))
+ .thenReturn(new BluetoothSinkAudioPolicy.Builder()
+ .setCallEstablishPolicy(BluetoothSinkAudioPolicy.POLICY_ALLOWED)
+ .setActiveDevicePolicyAfterConnection(
+ BluetoothSinkAudioPolicy.POLICY_NOT_ALLOWED)
+ .setInBandRingtonePolicy(BluetoothSinkAudioPolicy.POLICY_ALLOWED)
+ .build());
+
+ headsetConnected(mHeadsetDevice);
+ verify(mHeadsetService, never()).setActiveDevice(mHeadsetDevice);
+ }
+
+ /**
* A combo (A2DP + Headset) device is connected. Then a Hearing Aid is connected.
*/
@Test
diff --git a/android/app/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceFactoryResetTest.java b/android/app/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceFactoryResetTest.java
new file mode 100644
index 0000000..6acc5a0
--- /dev/null
+++ b/android/app/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceFactoryResetTest.java
@@ -0,0 +1,477 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.btservice;
+
+import static android.Manifest.permission.BLUETOOTH_SCAN;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+import android.app.AlarmManager;
+import android.app.admin.DevicePolicyManager;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.IBluetoothCallback;
+import android.content.AttributionSource;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PermissionInfo;
+import android.content.res.Resources;
+import android.media.AudioManager;
+import android.os.AsyncTask;
+import android.os.BatteryStatsManager;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.permission.PermissionCheckerManager;
+import android.permission.PermissionManager;
+import android.provider.Settings;
+import android.test.mock.MockContentProvider;
+import android.test.mock.MockContentResolver;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.bluetooth.TestUtils;
+import com.android.bluetooth.Utils;
+import com.android.bluetooth.a2dp.A2dpService;
+import com.android.bluetooth.a2dpsink.A2dpSinkService;
+import com.android.bluetooth.avrcp.AvrcpTargetService;
+import com.android.bluetooth.avrcpcontroller.AvrcpControllerService;
+import com.android.bluetooth.bas.BatteryService;
+import com.android.bluetooth.bass_client.BassClientService;
+import com.android.bluetooth.csip.CsipSetCoordinatorService;
+import com.android.bluetooth.gatt.GattService;
+import com.android.bluetooth.hap.HapClientService;
+import com.android.bluetooth.hearingaid.HearingAidService;
+import com.android.bluetooth.hfp.HeadsetService;
+import com.android.bluetooth.hfpclient.HeadsetClientService;
+import com.android.bluetooth.hid.HidDeviceService;
+import com.android.bluetooth.hid.HidHostService;
+import com.android.bluetooth.le_audio.LeAudioService;
+import com.android.bluetooth.map.BluetoothMapService;
+import com.android.bluetooth.mapclient.MapClientService;
+import com.android.bluetooth.mcp.McpService;
+import com.android.bluetooth.opp.BluetoothOppService;
+import com.android.bluetooth.pan.PanService;
+import com.android.bluetooth.pbap.BluetoothPbapService;
+import com.android.bluetooth.pbapclient.PbapClientService;
+import com.android.bluetooth.sap.SapService;
+import com.android.bluetooth.tbs.TbsService;
+import com.android.bluetooth.vc.VolumeControlService;
+import com.android.internal.app.IBatteryStats;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+import java.util.HashMap;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class AdapterServiceFactoryResetTest {
+ private static final String TAG = AdapterServiceFactoryResetTest.class.getSimpleName();
+
+ private AdapterService mAdapterService;
+ private AdapterService.AdapterServiceBinder mServiceBinder;
+
+ private @Mock Context mMockContext;
+ private @Mock ApplicationInfo mMockApplicationInfo;
+ private @Mock AlarmManager mMockAlarmManager;
+ private @Mock Resources mMockResources;
+ private @Mock UserManager mMockUserManager;
+ private @Mock DevicePolicyManager mMockDevicePolicyManager;
+ private @Mock ProfileService mMockGattService;
+ private @Mock ProfileService mMockService;
+ private @Mock ProfileService mMockService2;
+ private @Mock IBluetoothCallback mIBluetoothCallback;
+ private @Mock Binder mBinder;
+ private @Mock AudioManager mAudioManager;
+ private @Mock android.app.Application mApplication;
+ private @Mock MetricsLogger mMockMetricsLogger;
+
+ // BatteryStatsManager is final and cannot be mocked with regular mockito, so just mock the
+ // underlying binder calls.
+ final BatteryStatsManager mBatteryStatsManager =
+ new BatteryStatsManager(mock(IBatteryStats.class));
+
+ private static final int CONTEXT_SWITCH_MS = 100;
+ private static final int PROFILE_SERVICE_TOGGLE_TIME_MS = 200;
+ private static final int GATT_START_TIME_MS = 1000;
+ private static final int ONE_SECOND_MS = 1000;
+ private static final int NATIVE_INIT_MS = 8000;
+
+ private final AttributionSource mAttributionSource = new AttributionSource.Builder(
+ Process.myUid()).build();
+
+ private BluetoothManager mBluetoothManager;
+ private PowerManager mPowerManager;
+ private PermissionCheckerManager mPermissionCheckerManager;
+ private PermissionManager mPermissionManager;
+ private PackageManager mMockPackageManager;
+ private MockContentResolver mMockContentResolver;
+ private HashMap<String, HashMap<String, String>> mAdapterConfig;
+ private int mForegroundUserId;
+
+ private void configureEnabledProfiles() {
+ Log.e(TAG, "configureEnabledProfiles");
+ Config.setProfileEnabled(PanService.class, true);
+ Config.setProfileEnabled(BluetoothPbapService.class, true);
+ Config.setProfileEnabled(GattService.class, true);
+
+ Config.setProfileEnabled(A2dpService.class, false);
+ Config.setProfileEnabled(A2dpSinkService.class, false);
+ Config.setProfileEnabled(AvrcpTargetService.class, false);
+ Config.setProfileEnabled(AvrcpControllerService.class, false);
+ Config.setProfileEnabled(BassClientService.class, false);
+ Config.setProfileEnabled(BatteryService.class, false);
+ Config.setProfileEnabled(CsipSetCoordinatorService.class, false);
+ Config.setProfileEnabled(HapClientService.class, false);
+ Config.setProfileEnabled(HeadsetService.class, false);
+ Config.setProfileEnabled(HeadsetClientService.class, false);
+ Config.setProfileEnabled(HearingAidService.class, false);
+ Config.setProfileEnabled(HidDeviceService.class, false);
+ Config.setProfileEnabled(HidHostService.class, false);
+ Config.setProfileEnabled(LeAudioService.class, false);
+ Config.setProfileEnabled(TbsService.class, false);
+ Config.setProfileEnabled(BluetoothMapService.class, false);
+ Config.setProfileEnabled(MapClientService.class, false);
+ Config.setProfileEnabled(McpService.class, false);
+ Config.setProfileEnabled(BluetoothOppService.class, false);
+ Config.setProfileEnabled(PbapClientService.class, false);
+ Config.setProfileEnabled(SapService.class, false);
+ Config.setProfileEnabled(VolumeControlService.class, false);
+ }
+
+ @BeforeClass
+ public static void setupClass() {
+ Log.e(TAG, "setupClass");
+ // Bring native layer up and down to make sure config files are properly loaded
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+ Assert.assertNotNull(Looper.myLooper());
+ AdapterService adapterService = new AdapterService();
+ adapterService.initNative(false /* is_restricted */, false /* is_common_criteria_mode */,
+ 0 /* config_compare_result */, new String[0], false, "");
+ adapterService.cleanupNative();
+ HashMap<String, HashMap<String, String>> adapterConfig = TestUtils.readAdapterConfig();
+ Assert.assertNotNull(adapterConfig);
+ Assert.assertNotNull("metrics salt is null: " + adapterConfig.toString(),
+ AdapterServiceTest.getMetricsSalt(adapterConfig));
+ }
+
+ @Before
+ public void setUp() throws PackageManager.NameNotFoundException {
+ Log.e(TAG, "setUp()");
+ MockitoAnnotations.initMocks(this);
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+ Assert.assertNotNull(Looper.myLooper());
+
+ // Dispatch all async work through instrumentation so we can wait until
+ // it's drained below
+ AsyncTask.setDefaultExecutor((r) -> {
+ androidx.test.platform.app.InstrumentationRegistry.getInstrumentation()
+ .runOnMainSync(r);
+ });
+ androidx.test.platform.app.InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .adoptShellPermissionIdentity();
+
+ androidx.test.platform.app.InstrumentationRegistry.getInstrumentation().runOnMainSync(
+ () -> mAdapterService = new AdapterService());
+ mServiceBinder = new AdapterService.AdapterServiceBinder(mAdapterService);
+ mMockPackageManager = mock(PackageManager.class);
+ when(mMockPackageManager.getPermissionInfo(any(), anyInt()))
+ .thenReturn(new PermissionInfo());
+
+ mMockContentResolver = new MockContentResolver(InstrumentationRegistry.getTargetContext());
+ mMockContentResolver.addProvider(Settings.AUTHORITY, new MockContentProvider() {
+ @Override
+ public Bundle call(String method, String request, Bundle args) {
+ return Bundle.EMPTY;
+ }
+ });
+
+ mPowerManager = InstrumentationRegistry.getTargetContext()
+ .getSystemService(PowerManager.class);
+ mPermissionCheckerManager = InstrumentationRegistry.getTargetContext()
+ .getSystemService(PermissionCheckerManager.class);
+
+ mPermissionManager = InstrumentationRegistry.getTargetContext()
+ .getSystemService(PermissionManager.class);
+
+ mBluetoothManager = InstrumentationRegistry.getTargetContext()
+ .getSystemService(BluetoothManager.class);
+
+ when(mMockContext.getCacheDir()).thenReturn(InstrumentationRegistry.getTargetContext()
+ .getCacheDir());
+ when(mMockContext.getApplicationInfo()).thenReturn(mMockApplicationInfo);
+ when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver);
+ when(mMockContext.getApplicationContext()).thenReturn(mMockContext);
+ when(mMockContext.createContextAsUser(UserHandle.SYSTEM, /* flags= */ 0)).thenReturn(
+ mMockContext);
+ when(mMockContext.getResources()).thenReturn(mMockResources);
+ when(mMockContext.getUserId()).thenReturn(Process.BLUETOOTH_UID);
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockContext.getSystemService(Context.USER_SERVICE)).thenReturn(mMockUserManager);
+ when(mMockContext.getSystemServiceName(UserManager.class)).thenReturn(Context.USER_SERVICE);
+ when(mMockContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn(
+ mMockDevicePolicyManager);
+ when(mMockContext.getSystemServiceName(DevicePolicyManager.class))
+ .thenReturn(Context.DEVICE_POLICY_SERVICE);
+ when(mMockContext.getSystemService(Context.POWER_SERVICE)).thenReturn(mPowerManager);
+ when(mMockContext.getSystemServiceName(PowerManager.class))
+ .thenReturn(Context.POWER_SERVICE);
+ when(mMockContext.getSystemServiceName(PermissionCheckerManager.class))
+ .thenReturn(Context.PERMISSION_CHECKER_SERVICE);
+ when(mMockContext.getSystemService(Context.PERMISSION_CHECKER_SERVICE))
+ .thenReturn(mPermissionCheckerManager);
+ when(mMockContext.getSystemServiceName(PermissionManager.class))
+ .thenReturn(Context.PERMISSION_SERVICE);
+ when(mMockContext.getSystemService(Context.PERMISSION_SERVICE))
+ .thenReturn(mPermissionManager);
+ when(mMockContext.getSystemService(Context.ALARM_SERVICE)).thenReturn(mMockAlarmManager);
+ when(mMockContext.getSystemServiceName(AlarmManager.class))
+ .thenReturn(Context.ALARM_SERVICE);
+ when(mMockContext.getSystemService(Context.AUDIO_SERVICE)).thenReturn(mAudioManager);
+ when(mMockContext.getSystemServiceName(AudioManager.class))
+ .thenReturn(Context.AUDIO_SERVICE);
+ when(mMockContext.getSystemService(Context.BATTERY_STATS_SERVICE))
+ .thenReturn(mBatteryStatsManager);
+ when(mMockContext.getSystemServiceName(BatteryStatsManager.class))
+ .thenReturn(Context.BATTERY_STATS_SERVICE);
+ when(mMockContext.getSystemService(Context.BLUETOOTH_SERVICE))
+ .thenReturn(mBluetoothManager);
+ when(mMockContext.getSystemServiceName(BluetoothManager.class))
+ .thenReturn(Context.BLUETOOTH_SERVICE);
+ when(mMockContext.getSharedPreferences(anyString(), anyInt()))
+ .thenReturn(InstrumentationRegistry.getTargetContext()
+ .getSharedPreferences("AdapterServiceTestPrefs", Context.MODE_PRIVATE));
+
+ when(mMockContext.getAttributionSource()).thenReturn(mAttributionSource);
+ doAnswer(invocation -> {
+ Object[] args = invocation.getArguments();
+ return InstrumentationRegistry.getTargetContext().getDatabasePath((String) args[0]);
+ }).when(mMockContext).getDatabasePath(anyString());
+
+ // Sets the foreground user id to match that of the tests (restored in tearDown)
+ mForegroundUserId = Utils.getForegroundUserId();
+ int callingUid = Binder.getCallingUid();
+ UserHandle callingUser = UserHandle.getUserHandleForUid(callingUid);
+ Utils.setForegroundUserId(callingUser.getIdentifier());
+
+ when(mMockDevicePolicyManager.isCommonCriteriaModeEnabled(any())).thenReturn(false);
+
+ when(mIBluetoothCallback.asBinder()).thenReturn(mBinder);
+
+ doReturn(Process.BLUETOOTH_UID).when(mMockPackageManager)
+ .getPackageUidAsUser(any(), anyInt(), anyInt());
+
+ when(mMockGattService.getName()).thenReturn("GattService");
+ when(mMockService.getName()).thenReturn("Service1");
+ when(mMockService2.getName()).thenReturn("Service2");
+
+ when(mMockMetricsLogger.init(any())).thenReturn(true);
+ when(mMockMetricsLogger.close()).thenReturn(true);
+
+ configureEnabledProfiles();
+ Config.init(mMockContext);
+
+ mAdapterService.setMetricsLogger(mMockMetricsLogger);
+
+ // Attach a context to the service for permission checks.
+ mAdapterService.attach(mMockContext, null, null, null, mApplication, null);
+ mAdapterService.onCreate();
+
+ // Wait for any async events to drain
+ androidx.test.platform.app.InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+ mServiceBinder.registerCallback(mIBluetoothCallback, mAttributionSource);
+
+ mAdapterConfig = TestUtils.readAdapterConfig();
+ Assert.assertNotNull(mAdapterConfig);
+ }
+
+ @After
+ public void tearDown() {
+ Log.e(TAG, "tearDown()");
+
+ // Enable the stack to re-create the config. Next tests rely on it.
+ doEnable(0, false);
+
+ // Restores the foregroundUserId to the ID prior to the test setup
+ Utils.setForegroundUserId(mForegroundUserId);
+
+ mServiceBinder.unregisterCallback(mIBluetoothCallback, mAttributionSource);
+ mAdapterService.cleanup();
+ }
+
+ @AfterClass
+ public static void tearDownOnce() {
+ AsyncTask.setDefaultExecutor(AsyncTask.SERIAL_EXECUTOR);
+ }
+
+ private void verifyStateChange(int prevState, int currState, int callNumber, int timeoutMs) {
+ try {
+ verify(mIBluetoothCallback, timeout(timeoutMs).times(callNumber))
+ .onBluetoothStateChange(prevState, currState);
+ } catch (RemoteException e) {
+ // the mocked onBluetoothStateChange doesn't throw RemoteException
+ }
+ }
+
+ private void doEnable(int invocationNumber, boolean onlyGatt) {
+ Log.e(TAG, "doEnable() start");
+ Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
+
+ int startServiceCalls;
+ startServiceCalls = 2 * (onlyGatt ? 1 : 3); // Start and stop GATT + 2
+
+ mAdapterService.enable(false);
+
+ verifyStateChange(BluetoothAdapter.STATE_OFF, BluetoothAdapter.STATE_BLE_TURNING_ON,
+ invocationNumber + 1, CONTEXT_SWITCH_MS);
+
+ // Start GATT
+ verify(mMockContext, timeout(GATT_START_TIME_MS).times(
+ startServiceCalls * invocationNumber + 1)).startService(any());
+ mAdapterService.addProfile(mMockGattService);
+ mAdapterService.onProfileServiceStateChanged(mMockGattService, BluetoothAdapter.STATE_ON);
+
+ verifyStateChange(BluetoothAdapter.STATE_BLE_TURNING_ON, BluetoothAdapter.STATE_BLE_ON,
+ invocationNumber + 1, NATIVE_INIT_MS);
+
+ mServiceBinder.onLeServiceUp(mAttributionSource);
+
+ verifyStateChange(BluetoothAdapter.STATE_BLE_ON, BluetoothAdapter.STATE_TURNING_ON,
+ invocationNumber + 1, CONTEXT_SWITCH_MS);
+
+ if (!onlyGatt) {
+ // Start Mock PBAP and PAN services
+ verify(mMockContext, timeout(ONE_SECOND_MS).times(
+ startServiceCalls * invocationNumber + 3)).startService(any());
+
+ mAdapterService.addProfile(mMockService);
+ mAdapterService.addProfile(mMockService2);
+ mAdapterService.onProfileServiceStateChanged(mMockService, BluetoothAdapter.STATE_ON);
+ mAdapterService.onProfileServiceStateChanged(mMockService2, BluetoothAdapter.STATE_ON);
+ }
+
+ verifyStateChange(BluetoothAdapter.STATE_TURNING_ON, BluetoothAdapter.STATE_ON,
+ invocationNumber + 1, PROFILE_SERVICE_TOGGLE_TIME_MS);
+
+ verify(mMockContext, timeout(CONTEXT_SWITCH_MS).times(2 * invocationNumber + 2))
+ .sendBroadcast(any(), eq(BLUETOOTH_SCAN),
+ any(Bundle.class));
+ final int scanMode = mServiceBinder.getScanMode(mAttributionSource);
+ Assert.assertTrue(scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE
+ || scanMode == BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE);
+ Assert.assertTrue(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
+
+ Log.e(TAG, "doEnable() complete success");
+ }
+
+ /**
+ * Test: Verify that obfuscated Bluetooth address changes after factory reset
+ *
+ * There are 4 types of factory reset that we are talking about:
+ * 1. Factory reset all user data from Settings -> Will restart phone
+ * 2. Factory reset WiFi and Bluetooth from Settings -> Will only restart WiFi and BT
+ * 3. Call BluetoothAdapter.factoryReset() -> Will disable Bluetooth and reset config in
+ * memory and disk
+ * 4. Call AdapterService.factoryReset() -> Will only reset config in memory
+ *
+ * We can only use No. 4 here
+ */
+ @Ignore("AdapterService.factoryReset() does not reload config into memory and hence old salt"
+ + " is still used until next time Bluetooth library is initialized. However Bluetooth"
+ + " cannot be used until Bluetooth process restart any way. Thus it is almost"
+ + " guaranteed that user has to re-enable Bluetooth and hence re-generate new salt"
+ + " after factory reset")
+ @Test
+ public void testObfuscateBluetoothAddress_FactoryReset() {
+ Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
+ BluetoothDevice device = TestUtils.getTestDevice(BluetoothAdapter.getDefaultAdapter(), 0);
+ byte[] obfuscatedAddress1 = mAdapterService.obfuscateAddress(device);
+ Assert.assertTrue(obfuscatedAddress1.length > 0);
+ Assert.assertFalse(AdapterServiceTest.isByteArrayAllZero(obfuscatedAddress1));
+ mServiceBinder.factoryReset(mAttributionSource);
+ byte[] obfuscatedAddress2 = mAdapterService.obfuscateAddress(device);
+ Assert.assertTrue(obfuscatedAddress2.length > 0);
+ Assert.assertFalse(AdapterServiceTest.isByteArrayAllZero(obfuscatedAddress2));
+ Assert.assertFalse(Arrays.equals(obfuscatedAddress2,
+ obfuscatedAddress1));
+ doEnable(0, false);
+ byte[] obfuscatedAddress3 = mAdapterService.obfuscateAddress(device);
+ Assert.assertTrue(obfuscatedAddress3.length > 0);
+ Assert.assertFalse(AdapterServiceTest.isByteArrayAllZero(obfuscatedAddress3));
+ Assert.assertArrayEquals(obfuscatedAddress3,
+ obfuscatedAddress2);
+ mServiceBinder.factoryReset(mAttributionSource);
+ byte[] obfuscatedAddress4 = mAdapterService.obfuscateAddress(device);
+ Assert.assertTrue(obfuscatedAddress4.length > 0);
+ Assert.assertFalse(AdapterServiceTest.isByteArrayAllZero(obfuscatedAddress4));
+ Assert.assertFalse(Arrays.equals(obfuscatedAddress4,
+ obfuscatedAddress3));
+ }
+
+ /**
+ * Test: Verify that obfuscated Bluetooth address changes after factory reset and reloading
+ * native layer
+ */
+ @Test
+ public void testObfuscateBluetoothAddress_FactoryResetAndReloadNativeLayer() throws
+ PackageManager.NameNotFoundException {
+ byte[] metricsSalt1 = AdapterServiceTest.getMetricsSalt(mAdapterConfig);
+ Assert.assertNotNull(metricsSalt1);
+ Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
+ BluetoothDevice device = TestUtils.getTestDevice(BluetoothAdapter.getDefaultAdapter(), 0);
+ byte[] obfuscatedAddress1 = mAdapterService.obfuscateAddress(device);
+ Assert.assertTrue(obfuscatedAddress1.length > 0);
+ Assert.assertFalse(AdapterServiceTest.isByteArrayAllZero(obfuscatedAddress1));
+ Assert.assertArrayEquals(AdapterServiceTest.obfuscateInJava(metricsSalt1, device),
+ obfuscatedAddress1);
+ mServiceBinder.factoryReset(mAttributionSource);
+ tearDown();
+ setUp();
+ // Cannot verify metrics salt since it is not written to disk until native cleanup
+ byte[] obfuscatedAddress2 = mAdapterService.obfuscateAddress(device);
+ Assert.assertTrue(obfuscatedAddress2.length > 0);
+ Assert.assertFalse(AdapterServiceTest.isByteArrayAllZero(obfuscatedAddress2));
+ Assert.assertFalse(Arrays.equals(obfuscatedAddress2,
+ obfuscatedAddress1));
+ }
+}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceRestartTest.java b/android/app/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceRestartTest.java
new file mode 100644
index 0000000..c93837b
--- /dev/null
+++ b/android/app/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceRestartTest.java
@@ -0,0 +1,374 @@
+/*
+ * Copyright 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.btservice;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+import android.app.AlarmManager;
+import android.app.admin.DevicePolicyManager;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.IBluetoothCallback;
+import android.content.AttributionSource;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PermissionInfo;
+import android.content.res.Resources;
+import android.media.AudioManager;
+import android.os.AsyncTask;
+import android.os.BatteryStatsManager;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Looper;
+import android.os.PowerManager;
+import android.os.Process;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.permission.PermissionCheckerManager;
+import android.permission.PermissionManager;
+import android.provider.Settings;
+import android.test.mock.MockContentProvider;
+import android.test.mock.MockContentResolver;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.bluetooth.TestUtils;
+import com.android.bluetooth.Utils;
+import com.android.bluetooth.a2dp.A2dpService;
+import com.android.bluetooth.a2dpsink.A2dpSinkService;
+import com.android.bluetooth.avrcp.AvrcpTargetService;
+import com.android.bluetooth.avrcpcontroller.AvrcpControllerService;
+import com.android.bluetooth.bas.BatteryService;
+import com.android.bluetooth.bass_client.BassClientService;
+import com.android.bluetooth.csip.CsipSetCoordinatorService;
+import com.android.bluetooth.gatt.GattService;
+import com.android.bluetooth.hap.HapClientService;
+import com.android.bluetooth.hearingaid.HearingAidService;
+import com.android.bluetooth.hfp.HeadsetService;
+import com.android.bluetooth.hfpclient.HeadsetClientService;
+import com.android.bluetooth.hid.HidDeviceService;
+import com.android.bluetooth.hid.HidHostService;
+import com.android.bluetooth.le_audio.LeAudioService;
+import com.android.bluetooth.map.BluetoothMapService;
+import com.android.bluetooth.mapclient.MapClientService;
+import com.android.bluetooth.mcp.McpService;
+import com.android.bluetooth.opp.BluetoothOppService;
+import com.android.bluetooth.pan.PanService;
+import com.android.bluetooth.pbap.BluetoothPbapService;
+import com.android.bluetooth.pbapclient.PbapClientService;
+import com.android.bluetooth.sap.SapService;
+import com.android.bluetooth.tbs.TbsService;
+import com.android.bluetooth.vc.VolumeControlService;
+import com.android.internal.app.IBatteryStats;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.HashMap;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class AdapterServiceRestartTest {
+ private static final String TAG = AdapterServiceTest.class.getSimpleName();
+
+ private AdapterService mAdapterService;
+ private AdapterService.AdapterServiceBinder mServiceBinder;
+
+ private @Mock Context mMockContext;
+ private @Mock ApplicationInfo mMockApplicationInfo;
+ private @Mock AlarmManager mMockAlarmManager;
+ private @Mock Resources mMockResources;
+ private @Mock UserManager mMockUserManager;
+ private @Mock DevicePolicyManager mMockDevicePolicyManager;
+ private @Mock IBluetoothCallback mIBluetoothCallback;
+ private @Mock Binder mBinder;
+ private @Mock AudioManager mAudioManager;
+ private @Mock android.app.Application mApplication;
+ private @Mock MetricsLogger mMockMetricsLogger;
+
+ // BatteryStatsManager is final and cannot be mocked with regular mockito, so just mock the
+ // underlying binder calls.
+ final BatteryStatsManager mBatteryStatsManager =
+ new BatteryStatsManager(mock(IBatteryStats.class));
+
+ private final AttributionSource mAttributionSource = new AttributionSource.Builder(
+ Process.myUid()).build();
+
+ private BluetoothManager mBluetoothManager;
+ private PowerManager mPowerManager;
+ private PermissionCheckerManager mPermissionCheckerManager;
+ private PermissionManager mPermissionManager;
+ private PackageManager mMockPackageManager;
+ private MockContentResolver mMockContentResolver;
+ private HashMap<String, HashMap<String, String>> mAdapterConfig;
+ private int mForegroundUserId;
+
+ private void configureEnabledProfiles() {
+ Log.e(TAG, "configureEnabledProfiles");
+ Config.setProfileEnabled(PanService.class, true);
+ Config.setProfileEnabled(BluetoothPbapService.class, true);
+ Config.setProfileEnabled(GattService.class, true);
+
+ Config.setProfileEnabled(A2dpService.class, false);
+ Config.setProfileEnabled(A2dpSinkService.class, false);
+ Config.setProfileEnabled(AvrcpTargetService.class, false);
+ Config.setProfileEnabled(AvrcpControllerService.class, false);
+ Config.setProfileEnabled(BassClientService.class, false);
+ Config.setProfileEnabled(BatteryService.class, false);
+ Config.setProfileEnabled(CsipSetCoordinatorService.class, false);
+ Config.setProfileEnabled(HapClientService.class, false);
+ Config.setProfileEnabled(HeadsetService.class, false);
+ Config.setProfileEnabled(HeadsetClientService.class, false);
+ Config.setProfileEnabled(HearingAidService.class, false);
+ Config.setProfileEnabled(HidDeviceService.class, false);
+ Config.setProfileEnabled(HidHostService.class, false);
+ Config.setProfileEnabled(LeAudioService.class, false);
+ Config.setProfileEnabled(TbsService.class, false);
+ Config.setProfileEnabled(BluetoothMapService.class, false);
+ Config.setProfileEnabled(MapClientService.class, false);
+ Config.setProfileEnabled(McpService.class, false);
+ Config.setProfileEnabled(BluetoothOppService.class, false);
+ Config.setProfileEnabled(PbapClientService.class, false);
+ Config.setProfileEnabled(SapService.class, false);
+ Config.setProfileEnabled(VolumeControlService.class, false);
+ }
+
+ @BeforeClass
+ public static void setupClass() {
+ Log.e(TAG, "setupClass");
+ // Bring native layer up and down to make sure config files are properly loaded
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+ Assert.assertNotNull(Looper.myLooper());
+ AdapterService adapterService = new AdapterService();
+ adapterService.initNative(false /* is_restricted */, false /* is_common_criteria_mode */,
+ 0 /* config_compare_result */, new String[0], false, "");
+ adapterService.cleanupNative();
+ HashMap<String, HashMap<String, String>> adapterConfig = TestUtils.readAdapterConfig();
+ Assert.assertNotNull(adapterConfig);
+ Assert.assertNotNull("metrics salt is null: " + adapterConfig.toString(),
+ AdapterServiceTest.getMetricsSalt(adapterConfig));
+ }
+
+ @Before
+ public void setUp() throws PackageManager.NameNotFoundException {
+ Log.e(TAG, "setUp()");
+ MockitoAnnotations.initMocks(this);
+ if (Looper.myLooper() == null) {
+ Looper.prepare();
+ }
+ Assert.assertNotNull(Looper.myLooper());
+
+ // Dispatch all async work through instrumentation so we can wait until
+ // it's drained below
+ AsyncTask.setDefaultExecutor((r) -> {
+ androidx.test.platform.app.InstrumentationRegistry.getInstrumentation()
+ .runOnMainSync(r);
+ });
+ androidx.test.platform.app.InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .adoptShellPermissionIdentity();
+
+ androidx.test.platform.app.InstrumentationRegistry.getInstrumentation().runOnMainSync(
+ () -> mAdapterService = new AdapterService());
+ mServiceBinder = new AdapterService.AdapterServiceBinder(mAdapterService);
+ mMockPackageManager = mock(PackageManager.class);
+ when(mMockPackageManager.getPermissionInfo(any(), anyInt()))
+ .thenReturn(new PermissionInfo());
+
+ mMockContentResolver = new MockContentResolver(InstrumentationRegistry.getTargetContext());
+ mMockContentResolver.addProvider(Settings.AUTHORITY, new MockContentProvider() {
+ @Override
+ public Bundle call(String method, String request, Bundle args) {
+ return Bundle.EMPTY;
+ }
+ });
+
+ mPowerManager = InstrumentationRegistry.getTargetContext()
+ .getSystemService(PowerManager.class);
+ mPermissionCheckerManager = InstrumentationRegistry.getTargetContext()
+ .getSystemService(PermissionCheckerManager.class);
+
+ mPermissionManager = InstrumentationRegistry.getTargetContext()
+ .getSystemService(PermissionManager.class);
+
+ mBluetoothManager = InstrumentationRegistry.getTargetContext()
+ .getSystemService(BluetoothManager.class);
+
+ when(mMockContext.getCacheDir()).thenReturn(InstrumentationRegistry.getTargetContext()
+ .getCacheDir());
+ when(mMockContext.getApplicationInfo()).thenReturn(mMockApplicationInfo);
+ when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver);
+ when(mMockContext.getApplicationContext()).thenReturn(mMockContext);
+ when(mMockContext.createContextAsUser(UserHandle.SYSTEM, /* flags= */ 0)).thenReturn(
+ mMockContext);
+ when(mMockContext.getResources()).thenReturn(mMockResources);
+ when(mMockContext.getUserId()).thenReturn(Process.BLUETOOTH_UID);
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockContext.getSystemService(Context.USER_SERVICE)).thenReturn(mMockUserManager);
+ when(mMockContext.getSystemServiceName(UserManager.class)).thenReturn(Context.USER_SERVICE);
+ when(mMockContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn(
+ mMockDevicePolicyManager);
+ when(mMockContext.getSystemServiceName(DevicePolicyManager.class))
+ .thenReturn(Context.DEVICE_POLICY_SERVICE);
+ when(mMockContext.getSystemService(Context.POWER_SERVICE)).thenReturn(mPowerManager);
+ when(mMockContext.getSystemServiceName(PowerManager.class))
+ .thenReturn(Context.POWER_SERVICE);
+ when(mMockContext.getSystemServiceName(PermissionCheckerManager.class))
+ .thenReturn(Context.PERMISSION_CHECKER_SERVICE);
+ when(mMockContext.getSystemService(Context.PERMISSION_CHECKER_SERVICE))
+ .thenReturn(mPermissionCheckerManager);
+ when(mMockContext.getSystemServiceName(PermissionManager.class))
+ .thenReturn(Context.PERMISSION_SERVICE);
+ when(mMockContext.getSystemService(Context.PERMISSION_SERVICE))
+ .thenReturn(mPermissionManager);
+ when(mMockContext.getSystemService(Context.ALARM_SERVICE)).thenReturn(mMockAlarmManager);
+ when(mMockContext.getSystemServiceName(AlarmManager.class))
+ .thenReturn(Context.ALARM_SERVICE);
+ when(mMockContext.getSystemService(Context.AUDIO_SERVICE)).thenReturn(mAudioManager);
+ when(mMockContext.getSystemServiceName(AudioManager.class))
+ .thenReturn(Context.AUDIO_SERVICE);
+ when(mMockContext.getSystemService(Context.BATTERY_STATS_SERVICE))
+ .thenReturn(mBatteryStatsManager);
+ when(mMockContext.getSystemServiceName(BatteryStatsManager.class))
+ .thenReturn(Context.BATTERY_STATS_SERVICE);
+ when(mMockContext.getSystemService(Context.BLUETOOTH_SERVICE))
+ .thenReturn(mBluetoothManager);
+ when(mMockContext.getSystemServiceName(BluetoothManager.class))
+ .thenReturn(Context.BLUETOOTH_SERVICE);
+ when(mMockContext.getSharedPreferences(anyString(), anyInt()))
+ .thenReturn(InstrumentationRegistry.getTargetContext()
+ .getSharedPreferences("AdapterServiceTestPrefs", Context.MODE_PRIVATE));
+
+ when(mMockContext.getAttributionSource()).thenReturn(mAttributionSource);
+ doAnswer(invocation -> {
+ Object[] args = invocation.getArguments();
+ return InstrumentationRegistry.getTargetContext().getDatabasePath((String) args[0]);
+ }).when(mMockContext).getDatabasePath(anyString());
+
+ // Sets the foreground user id to match that of the tests (restored in tearDown)
+ mForegroundUserId = Utils.getForegroundUserId();
+ int callingUid = Binder.getCallingUid();
+ UserHandle callingUser = UserHandle.getUserHandleForUid(callingUid);
+ Utils.setForegroundUserId(callingUser.getIdentifier());
+
+ when(mMockDevicePolicyManager.isCommonCriteriaModeEnabled(any())).thenReturn(false);
+
+ when(mIBluetoothCallback.asBinder()).thenReturn(mBinder);
+
+ doReturn(Process.BLUETOOTH_UID).when(mMockPackageManager)
+ .getPackageUidAsUser(any(), anyInt(), anyInt());
+
+ when(mMockMetricsLogger.init(any())).thenReturn(true);
+ when(mMockMetricsLogger.close()).thenReturn(true);
+
+ configureEnabledProfiles();
+ Config.init(mMockContext);
+
+ mAdapterService.setMetricsLogger(mMockMetricsLogger);
+
+ // Attach a context to the service for permission checks.
+ mAdapterService.attach(mMockContext, null, null, null, mApplication, null);
+ mAdapterService.onCreate();
+
+ // Wait for any async events to drain
+ androidx.test.platform.app.InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+ mServiceBinder.registerCallback(mIBluetoothCallback, mAttributionSource);
+
+ mAdapterConfig = TestUtils.readAdapterConfig();
+ Assert.assertNotNull(mAdapterConfig);
+ }
+
+ @After
+ public void tearDown() {
+ Log.e(TAG, "tearDown()");
+
+ // Restores the foregroundUserId to the ID prior to the test setup
+ Utils.setForegroundUserId(mForegroundUserId);
+
+ mServiceBinder.unregisterCallback(mIBluetoothCallback, mAttributionSource);
+ mAdapterService.cleanup();
+ }
+
+ @AfterClass
+ public static void tearDownOnce() {
+ AsyncTask.setDefaultExecutor(AsyncTask.SERIAL_EXECUTOR);
+ }
+
+ /**
+ * Test: Check if obfuscated Bluetooth address stays the same after re-initializing
+ * {@link AdapterService}
+ */
+ @Test
+ public void testObfuscateBluetoothAddress_PersistentBetweenAdapterServiceInitialization() throws
+ PackageManager.NameNotFoundException {
+ byte[] metricsSalt = AdapterServiceTest.getMetricsSalt(mAdapterConfig);
+ Assert.assertNotNull(metricsSalt);
+ Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
+ BluetoothDevice device = TestUtils.getTestDevice(BluetoothAdapter.getDefaultAdapter(), 0);
+ byte[] obfuscatedAddress1 = mAdapterService.obfuscateAddress(device);
+ Assert.assertTrue(obfuscatedAddress1.length > 0);
+ Assert.assertFalse(AdapterServiceTest.isByteArrayAllZero(obfuscatedAddress1));
+ Assert.assertArrayEquals(AdapterServiceTest.obfuscateInJava(metricsSalt, device),
+ obfuscatedAddress1);
+ tearDown();
+ setUp();
+
+ byte[] metricsSalt2 = AdapterServiceTest.getMetricsSalt(mAdapterConfig);
+ Assert.assertNotNull(metricsSalt2);
+ Assert.assertArrayEquals(metricsSalt, metricsSalt2);
+
+ Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
+ byte[] obfuscatedAddress2 = mAdapterService.obfuscateAddress(device);
+ Assert.assertTrue(obfuscatedAddress2.length > 0);
+ Assert.assertFalse(AdapterServiceTest.isByteArrayAllZero(obfuscatedAddress2));
+ Assert.assertArrayEquals(obfuscatedAddress2,
+ obfuscatedAddress1);
+ }
+
+ /**
+ * Test: Check if id gotten stays the same after re-initializing
+ * {@link AdapterService}
+ */
+ @Test
+ public void testgetMetricId_PersistentBetweenAdapterServiceInitialization() throws
+ PackageManager.NameNotFoundException {
+ Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
+ BluetoothDevice device = TestUtils.getTestDevice(BluetoothAdapter.getDefaultAdapter(), 0);
+ int id1 = mAdapterService.getMetricId(device);
+ Assert.assertTrue(id1 > 0);
+ tearDown();
+ setUp();
+ Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
+ int id2 = mAdapterService.getMetricId(device);
+ Assert.assertEquals(id2, id1);
+ }
+}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceTest.java
index 29414ca..c320b35 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceTest.java
@@ -101,7 +101,6 @@
import java.io.PrintWriter;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
-import java.util.Arrays;
import java.util.HashMap;
import javax.crypto.Mac;
@@ -280,6 +279,10 @@
.thenReturn(mBatteryStatsManager);
when(mMockContext.getSystemServiceName(BatteryStatsManager.class))
.thenReturn(Context.BATTERY_STATS_SERVICE);
+ when(mMockContext.getSharedPreferences(anyString(), anyInt()))
+ .thenReturn(InstrumentationRegistry.getTargetContext()
+ .getSharedPreferences("AdapterServiceTestPrefs", Context.MODE_PRIVATE));
+
when(mMockContext.getAttributionSource()).thenReturn(mAttributionSource);
doAnswer(invocation -> {
Object[] args = invocation.getArguments();
@@ -767,103 +770,6 @@
obfuscatedAddress1);
}
- /**
- * Test: Check if obfuscated Bluetooth address stays the same after re-initializing
- * {@link AdapterService}
- */
- @Test
- public void testObfuscateBluetoothAddress_PersistentBetweenAdapterServiceInitialization() throws
- PackageManager.NameNotFoundException {
- byte[] metricsSalt = getMetricsSalt(mAdapterConfig);
- Assert.assertNotNull(metricsSalt);
- Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
- BluetoothDevice device = TestUtils.getTestDevice(BluetoothAdapter.getDefaultAdapter(), 0);
- byte[] obfuscatedAddress1 = mAdapterService.obfuscateAddress(device);
- Assert.assertTrue(obfuscatedAddress1.length > 0);
- Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress1));
- Assert.assertArrayEquals(obfuscateInJava(metricsSalt, device),
- obfuscatedAddress1);
- tearDown();
- setUp();
- Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
- byte[] obfuscatedAddress2 = mAdapterService.obfuscateAddress(device);
- Assert.assertTrue(obfuscatedAddress2.length > 0);
- Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress2));
- Assert.assertArrayEquals(obfuscatedAddress2,
- obfuscatedAddress1);
- }
-
- /**
- * Test: Verify that obfuscated Bluetooth address changes after factory reset
- *
- * There are 4 types of factory reset that we are talking about:
- * 1. Factory reset all user data from Settings -> Will restart phone
- * 2. Factory reset WiFi and Bluetooth from Settings -> Will only restart WiFi and BT
- * 3. Call BluetoothAdapter.factoryReset() -> Will disable Bluetooth and reset config in
- * memory and disk
- * 4. Call AdapterService.factoryReset() -> Will only reset config in memory
- *
- * We can only use No. 4 here
- */
- @Ignore("AdapterService.factoryReset() does not reload config into memory and hence old salt"
- + " is still used until next time Bluetooth library is initialized. However Bluetooth"
- + " cannot be used until Bluetooth process restart any way. Thus it is almost"
- + " guaranteed that user has to re-enable Bluetooth and hence re-generate new salt"
- + " after factory reset")
- @Test
- public void testObfuscateBluetoothAddress_FactoryReset() {
- Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
- BluetoothDevice device = TestUtils.getTestDevice(BluetoothAdapter.getDefaultAdapter(), 0);
- byte[] obfuscatedAddress1 = mAdapterService.obfuscateAddress(device);
- Assert.assertTrue(obfuscatedAddress1.length > 0);
- Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress1));
- mServiceBinder.factoryReset(mAttributionSource);
- byte[] obfuscatedAddress2 = mAdapterService.obfuscateAddress(device);
- Assert.assertTrue(obfuscatedAddress2.length > 0);
- Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress2));
- Assert.assertFalse(Arrays.equals(obfuscatedAddress2,
- obfuscatedAddress1));
- doEnable(0, false);
- byte[] obfuscatedAddress3 = mAdapterService.obfuscateAddress(device);
- Assert.assertTrue(obfuscatedAddress3.length > 0);
- Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress3));
- Assert.assertArrayEquals(obfuscatedAddress3,
- obfuscatedAddress2);
- mServiceBinder.factoryReset(mAttributionSource);
- byte[] obfuscatedAddress4 = mAdapterService.obfuscateAddress(device);
- Assert.assertTrue(obfuscatedAddress4.length > 0);
- Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress4));
- Assert.assertFalse(Arrays.equals(obfuscatedAddress4,
- obfuscatedAddress3));
- }
-
- /**
- * Test: Verify that obfuscated Bluetooth address changes after factory reset and reloading
- * native layer
- */
- @Test
- public void testObfuscateBluetoothAddress_FactoryResetAndReloadNativeLayer() throws
- PackageManager.NameNotFoundException {
- byte[] metricsSalt1 = getMetricsSalt(mAdapterConfig);
- Assert.assertNotNull(metricsSalt1);
- Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
- BluetoothDevice device = TestUtils.getTestDevice(BluetoothAdapter.getDefaultAdapter(), 0);
- byte[] obfuscatedAddress1 = mAdapterService.obfuscateAddress(device);
- Assert.assertTrue(obfuscatedAddress1.length > 0);
- Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress1));
- Assert.assertArrayEquals(obfuscateInJava(metricsSalt1, device),
- obfuscatedAddress1);
- mServiceBinder.factoryReset(mAttributionSource);
- tearDown();
- setUp();
- // Cannot verify metrics salt since it is not written to disk until native cleanup
- byte[] obfuscatedAddress2 = mAdapterService.obfuscateAddress(device);
- Assert.assertTrue(obfuscatedAddress2.length > 0);
- Assert.assertFalse(isByteArrayAllZero(obfuscatedAddress2));
- Assert.assertFalse(Arrays.equals(obfuscatedAddress2,
- obfuscatedAddress1));
- }
-
@Test
public void testAddressConsolidation() {
// Create device properties
@@ -881,7 +787,7 @@
Assert.assertEquals(identityAddress, TEST_BT_ADDR_2);
}
- private static byte[] getMetricsSalt(HashMap<String, HashMap<String, String>> adapterConfig) {
+ public static byte[] getMetricsSalt(HashMap<String, HashMap<String, String>> adapterConfig) {
HashMap<String, String> metricsSection = adapterConfig.get("Metrics");
if (metricsSection == null) {
Log.e(TAG, "Metrics section is null: " + adapterConfig.toString());
@@ -900,7 +806,7 @@
return metricsSalt;
}
- private static byte[] obfuscateInJava(byte[] key, BluetoothDevice device) {
+ public static byte[] obfuscateInJava(byte[] key, BluetoothDevice device) {
String algorithm = "HmacSHA256";
try {
Mac hmac256 = Mac.getInstance(algorithm);
@@ -912,7 +818,7 @@
}
}
- private static boolean isByteArrayAllZero(byte[] byteArray) {
+ public static boolean isByteArrayAllZero(byte[] byteArray) {
for (byte i : byteArray) {
if (i != 0) {
return false;
@@ -980,24 +886,6 @@
Assert.assertEquals(id3, id1);
}
- /**
- * Test: Check if id gotten stays the same after re-initializing
- * {@link AdapterService}
- */
- @Test
- public void testgetMetricId_PersistentBetweenAdapterServiceInitialization() throws
- PackageManager.NameNotFoundException {
- Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
- BluetoothDevice device = TestUtils.getTestDevice(BluetoothAdapter.getDefaultAdapter(), 0);
- int id1 = mAdapterService.getMetricId(device);
- Assert.assertTrue(id1 > 0);
- tearDown();
- setUp();
- Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
- int id2 = mAdapterService.getMetricId(device);
- Assert.assertEquals(id2, id1);
- }
-
@Test
public void testDump_doesNotCrash() {
FileDescriptor fd = new FileDescriptor();
diff --git a/android/app/tests/unit/src/com/android/bluetooth/btservice/CompanionManagerTest.java b/android/app/tests/unit/src/com/android/bluetooth/btservice/CompanionManagerTest.java
new file mode 100644
index 0000000..41746f0
--- /dev/null
+++ b/android/app/tests/unit/src/com/android/bluetooth/btservice/CompanionManagerTest.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.bluetooth.btservice;
+
+import static org.mockito.Mockito.*;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.HandlerThread;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.bluetooth.TestUtils;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class CompanionManagerTest {
+
+ private static final String TEST_DEVICE = "11:22:33:44:55:66";
+
+ private AdapterProperties mAdapterProperties;
+ private Context mTargetContext;
+ private CompanionManager mCompanionManager;
+
+ private HandlerThread mHandlerThread;
+
+ @Mock
+ private AdapterService mAdapterService;
+ @Mock
+ SharedPreferences mSharedPreferences;
+ @Mock
+ SharedPreferences.Editor mEditor;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mTargetContext = InstrumentationRegistry.getTargetContext();
+ // Prepare the TestUtils
+ TestUtils.setAdapterService(mAdapterService);
+ // Start handler thread for this test
+ mHandlerThread = new HandlerThread("CompanionManagerTestHandlerThread");
+ mHandlerThread.start();
+ // Mock the looper
+ doReturn(mHandlerThread.getLooper()).when(mAdapterService).getMainLooper();
+ // Mock SharedPreferences
+ when(mSharedPreferences.edit()).thenReturn(mEditor);
+ doReturn(mSharedPreferences).when(mAdapterService).getSharedPreferences(eq(
+ CompanionManager.COMPANION_INFO), eq(Context.MODE_PRIVATE));
+ // Tell the AdapterService that it is a mock (see isMock documentation)
+ doReturn(true).when(mAdapterService).isMock();
+ // Use the resources in the instrumentation instead of the mocked AdapterService
+ when(mAdapterService.getResources()).thenReturn(mTargetContext.getResources());
+
+ // Must be called to initialize services
+ mCompanionManager = new CompanionManager(mAdapterService, null);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mHandlerThread.quit();
+ TestUtils.clearAdapterService(mAdapterService);
+ }
+
+ @Test
+ public void testLoadCompanionInfo_hasCompanionDeviceKey() {
+ loadCompanionInfoHelper(TEST_DEVICE, CompanionManager.COMPANION_TYPE_PRIMARY);
+ }
+
+ @Test
+ public void testLoadCompanionInfo_noCompanionDeviceSetButHaveBondedDevices_shouldNotCrash() {
+ BluetoothDevice[] devices = new BluetoothDevice[2];
+ doReturn(devices).when(mAdapterService).getBondedDevices();
+ doThrow(new IllegalArgumentException())
+ .when(mSharedPreferences)
+ .getInt(eq(CompanionManager.COMPANION_TYPE_KEY), anyInt());
+ mCompanionManager.loadCompanionInfo();
+ }
+
+ @Test
+ public void testIsCompanionDevice() {
+ loadCompanionInfoHelper(TEST_DEVICE, CompanionManager.COMPANION_TYPE_NONE);
+ Assert.assertTrue(mCompanionManager.isCompanionDevice(TEST_DEVICE));
+
+ loadCompanionInfoHelper(TEST_DEVICE, CompanionManager.COMPANION_TYPE_PRIMARY);
+ Assert.assertTrue(mCompanionManager.isCompanionDevice(TEST_DEVICE));
+
+ loadCompanionInfoHelper(TEST_DEVICE, CompanionManager.COMPANION_TYPE_SECONDARY);
+ Assert.assertTrue(mCompanionManager.isCompanionDevice(TEST_DEVICE));
+ }
+
+ @Test
+ public void testGetGattConnParameterPrimary() {
+ loadCompanionInfoHelper(TEST_DEVICE, CompanionManager.COMPANION_TYPE_PRIMARY);
+ checkReasonableConnParameterHelper(BluetoothGatt.CONNECTION_PRIORITY_HIGH);
+ checkReasonableConnParameterHelper(BluetoothGatt.CONNECTION_PRIORITY_BALANCED);
+ checkReasonableConnParameterHelper(BluetoothGatt.CONNECTION_PRIORITY_LOW_POWER);
+
+ loadCompanionInfoHelper(TEST_DEVICE, CompanionManager.COMPANION_TYPE_SECONDARY);
+ checkReasonableConnParameterHelper(BluetoothGatt.CONNECTION_PRIORITY_HIGH);
+ checkReasonableConnParameterHelper(BluetoothGatt.CONNECTION_PRIORITY_BALANCED);
+ checkReasonableConnParameterHelper(BluetoothGatt.CONNECTION_PRIORITY_LOW_POWER);
+
+ loadCompanionInfoHelper(TEST_DEVICE, CompanionManager.COMPANION_TYPE_NONE);
+ checkReasonableConnParameterHelper(BluetoothGatt.CONNECTION_PRIORITY_HIGH);
+ checkReasonableConnParameterHelper(BluetoothGatt.CONNECTION_PRIORITY_BALANCED);
+ checkReasonableConnParameterHelper(BluetoothGatt.CONNECTION_PRIORITY_LOW_POWER);
+ }
+
+ private void loadCompanionInfoHelper(String address, int companionType) {
+ doReturn(address)
+ .when(mSharedPreferences)
+ .getString(eq(CompanionManager.COMPANION_DEVICE_KEY), anyString());
+ doReturn(companionType)
+ .when(mSharedPreferences)
+ .getInt(eq(CompanionManager.COMPANION_TYPE_KEY), anyInt());
+ mCompanionManager.loadCompanionInfo();
+ }
+
+ private void checkReasonableConnParameterHelper(int priority) {
+ // Max/Min values from the Bluetooth spec Version 5.3 | Vol 4, Part E | 7.8.18
+ final int minInterval = 6; // 0x0006
+ final int maxInterval = 3200; // 0x0C80
+ final int minLatency = 0; // 0x0000
+ final int maxLatency = 499; // 0x01F3
+
+ int min = mCompanionManager.getGattConnParameters(
+ TEST_DEVICE, CompanionManager.GATT_CONN_INTERVAL_MIN,
+ priority);
+ int max = mCompanionManager.getGattConnParameters(
+ TEST_DEVICE, CompanionManager.GATT_CONN_INTERVAL_MAX,
+ priority);
+ int latency = mCompanionManager.getGattConnParameters(
+ TEST_DEVICE, CompanionManager.GATT_CONN_LATENCY,
+ priority);
+
+ Assert.assertTrue(max >= min);
+ Assert.assertTrue(max >= minInterval);
+ Assert.assertTrue(min >= minInterval);
+ Assert.assertTrue(max <= maxInterval);
+ Assert.assertTrue(min <= maxInterval);
+ Assert.assertTrue(latency >= minLatency);
+ Assert.assertTrue(latency <= maxLatency);
+ }
+}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/btservice/DataMigrationTest.java b/android/app/tests/unit/src/com/android/bluetooth/btservice/DataMigrationTest.java
index d7f9bfb..ea39f28 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/btservice/DataMigrationTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/btservice/DataMigrationTest.java
@@ -66,7 +66,7 @@
private static final String AUTHORITY = "bluetooth_legacy.provider";
- private static final String TEST_PREF = "TestPref";
+ private static final String TEST_PREF = "DatabaseTestPref";
private MockContentResolver mMockContentResolver;
@@ -82,6 +82,7 @@
mTargetContext = InstrumentationRegistry.getTargetContext();
mTargetContext.deleteSharedPreferences(TEST_PREF);
mPrefs = mTargetContext.getSharedPreferences(TEST_PREF, Context.MODE_PRIVATE);
+ mPrefs.edit().clear().apply();
mMockContentResolver = new MockContentResolver(mTargetContext);
when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver);
@@ -93,7 +94,8 @@
@After
public void tearDown() throws Exception {
- mTargetContext.deleteSharedPreferences("TestPref");
+ mPrefs.edit().clear().apply();
+ mTargetContext.deleteSharedPreferences(TEST_PREF);
mTargetContext.deleteDatabase("TestBluetoothDb");
mTargetContext.deleteDatabase("TestOppDb");
}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/btservice/MetricsLoggerTest.java b/android/app/tests/unit/src/com/android/bluetooth/btservice/MetricsLoggerTest.java
index 851e998..731580a 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/btservice/MetricsLoggerTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/btservice/MetricsLoggerTest.java
@@ -26,6 +26,8 @@
import com.android.bluetooth.BluetoothMetricsProto.ProfileConnectionStats;
import com.android.bluetooth.BluetoothMetricsProto.ProfileId;
+import com.google.common.hash.BloomFilter;
+import com.google.common.hash.Funnels;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
@@ -34,6 +36,9 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
import java.util.HashMap;
import java.util.List;
@@ -43,12 +48,15 @@
@MediumTest
@RunWith(AndroidJUnit4.class)
public class MetricsLoggerTest {
+ private static final String TEST_BLOOMFILTER_NAME = "TestBloomfilter";
+
private TestableMetricsLogger mTestableMetricsLogger;
@Mock
private AdapterService mMockAdapterService;
public class TestableMetricsLogger extends MetricsLogger {
public HashMap<Integer, Long> mTestableCounters = new HashMap<>();
+ public HashMap<String, Integer> mTestableDeviceNames = new HashMap<>();
@Override
public boolean count(int key, long count) {
@@ -63,6 +71,11 @@
@Override
protected void cancelPendingDrain() {
}
+
+ @Override
+ protected void statslogBluetoothDeviceNames(String matchedString, byte[] sha256) {
+ mTestableDeviceNames.merge(matchedString, 1, Integer::sum);
+ }
}
@Before
@@ -71,6 +84,7 @@
// Dump metrics to clean up internal states
MetricsLogger.dumpProto(BluetoothLog.newBuilder());
mTestableMetricsLogger = new TestableMetricsLogger();
+ mTestableMetricsLogger.mBloomFilterInitialized = true;
doReturn(null)
.when(mMockAdapterService).registerReceiver(any(), any());
}
@@ -207,4 +221,181 @@
Assert.assertTrue(mTestableMetricsLogger.isInitialized());
Assert.assertFalse(mTestableMetricsLogger.init(mMockAdapterService));
}
+
+ @Test
+ public void testDeviceNameUploadingDeviceSet1() {
+ initTestingBloomfitler();
+
+ mTestableMetricsLogger.logSanitizedBluetoothDeviceName("a b c d e f g h pixel 7");
+ Assert.assertTrue(mTestableMetricsLogger.mTestableDeviceNames.isEmpty());
+
+ mTestableMetricsLogger.logSanitizedBluetoothDeviceName("AirpoDspro");
+ Assert.assertEquals(1,
+ mTestableMetricsLogger.mTestableDeviceNames.get("airpodspro").intValue());
+
+ mTestableMetricsLogger.logSanitizedBluetoothDeviceName("AirpoDs-pro");
+ Assert.assertEquals(2,
+ mTestableMetricsLogger.mTestableDeviceNames.get("airpodspro").intValue());
+
+ mTestableMetricsLogger.logSanitizedBluetoothDeviceName("Someone's AirpoDs");
+ Assert.assertEquals(1,
+ mTestableMetricsLogger.mTestableDeviceNames.get("airpods").intValue());
+
+ mTestableMetricsLogger.logSanitizedBluetoothDeviceName("Who's Pixel 7");
+ Assert.assertEquals(1,
+ mTestableMetricsLogger.mTestableDeviceNames.get("pixel7").intValue());
+
+ mTestableMetricsLogger.logSanitizedBluetoothDeviceName("陈的pixel 7手机");
+ Assert.assertEquals(2,
+ mTestableMetricsLogger.mTestableDeviceNames.get("pixel7").intValue());
+
+ mTestableMetricsLogger.logSanitizedBluetoothDeviceName("pixel 7 pro");
+ Assert.assertEquals(1,
+ mTestableMetricsLogger.mTestableDeviceNames.get("pixel7pro").intValue());
+
+ mTestableMetricsLogger.logSanitizedBluetoothDeviceName("My Pixel 7 PRO");
+ Assert.assertEquals(2,
+ mTestableMetricsLogger.mTestableDeviceNames.get("pixel7pro").intValue());
+
+ mTestableMetricsLogger.logSanitizedBluetoothDeviceName("My Pixel 7 PRO");
+ Assert.assertEquals(3,
+ mTestableMetricsLogger.mTestableDeviceNames.get("pixel7pro").intValue());
+
+ mTestableMetricsLogger.logSanitizedBluetoothDeviceName("My Pixel 7 - PRO");
+ Assert.assertEquals(4,
+ mTestableMetricsLogger.mTestableDeviceNames.get("pixel7pro").intValue());
+
+ mTestableMetricsLogger.logSanitizedBluetoothDeviceName("My BMW X5");
+ Assert.assertEquals(1,
+ mTestableMetricsLogger.mTestableDeviceNames.get("bmwx5").intValue());
+
+ mTestableMetricsLogger.logSanitizedBluetoothDeviceName("Jane Doe's Tesla Model--X");
+ Assert.assertEquals(1,
+ mTestableMetricsLogger.mTestableDeviceNames.get("teslamodelx").intValue());
+
+ mTestableMetricsLogger.logSanitizedBluetoothDeviceName("TESLA of Jane DOE");
+ Assert.assertEquals(1,
+ mTestableMetricsLogger.mTestableDeviceNames.get("tesla").intValue());
+
+ mTestableMetricsLogger
+ .logSanitizedBluetoothDeviceName("SONY WH-1000XM noise cancelling headsets");
+ Assert.assertEquals(1,
+ mTestableMetricsLogger.mTestableDeviceNames.get("sonywh1000xm").intValue());
+
+ mTestableMetricsLogger
+ .logSanitizedBluetoothDeviceName("SONY WH-1000XM4 noise cancelling headsets");
+ Assert.assertEquals(1,
+ mTestableMetricsLogger.mTestableDeviceNames.get("sonywh1000xm4").intValue());
+
+ mTestableMetricsLogger
+ .logSanitizedBluetoothDeviceName("Amazon Echo Dot in Kitchen");
+ Assert.assertEquals(1,
+ mTestableMetricsLogger.mTestableDeviceNames.get("amazonechodot").intValue());
+
+ mTestableMetricsLogger
+ .logSanitizedBluetoothDeviceName("斯巴鲁 Starlink");
+ Assert.assertEquals(1,
+ mTestableMetricsLogger.mTestableDeviceNames.get("starlink").intValue());
+
+ mTestableMetricsLogger
+ .logSanitizedBluetoothDeviceName("大黄蜂MyLink");
+ Assert.assertEquals(1,
+ mTestableMetricsLogger.mTestableDeviceNames.get("mylink").intValue());
+
+ mTestableMetricsLogger
+ .logSanitizedBluetoothDeviceName("Dad's Fitbit Charge 3");
+ Assert.assertEquals(1,
+ mTestableMetricsLogger.mTestableDeviceNames.get("fitbitcharge3").intValue());
+
+ mTestableMetricsLogger.mTestableDeviceNames.clear();
+ mTestableMetricsLogger.logSanitizedBluetoothDeviceName("");
+ Assert.assertTrue(mTestableMetricsLogger.mTestableDeviceNames.isEmpty());
+
+ mTestableMetricsLogger.logSanitizedBluetoothDeviceName(" ");
+ Assert.assertTrue(mTestableMetricsLogger.mTestableDeviceNames.isEmpty());
+
+ mTestableMetricsLogger.logSanitizedBluetoothDeviceName("SomeDevice1");
+ Assert.assertTrue(mTestableMetricsLogger.mTestableDeviceNames.isEmpty());
+
+ mTestableMetricsLogger.logSanitizedBluetoothDeviceName("Bluetooth headset");
+ Assert.assertTrue(mTestableMetricsLogger.mTestableDeviceNames.isEmpty());
+
+ mTestableMetricsLogger.logSanitizedBluetoothDeviceName("Some Device-2");
+ Assert.assertTrue(mTestableMetricsLogger.mTestableDeviceNames.isEmpty());
+
+ mTestableMetricsLogger.logSanitizedBluetoothDeviceName("abcgfDG gdfg");
+ Assert.assertTrue(mTestableMetricsLogger.mTestableDeviceNames.isEmpty());
+ }
+
+ @Test
+ public void testDeviceNameUploadingDeviceSet2() {
+ initTestingBloomfitler();
+
+ mTestableMetricsLogger
+ .logSanitizedBluetoothDeviceName("Galaxy Buds pro");
+ Assert.assertEquals(1,
+ mTestableMetricsLogger.mTestableDeviceNames.get("galaxybudspro").intValue());
+
+ mTestableMetricsLogger
+ .logSanitizedBluetoothDeviceName("Mike's new Galaxy Buds 2");
+ Assert.assertEquals(1,
+ mTestableMetricsLogger.mTestableDeviceNames.get("galaxybuds2").intValue());
+
+ mTestableMetricsLogger
+ .logSanitizedBluetoothDeviceName("My third Ford F-150");
+ Assert.assertEquals(1,
+ mTestableMetricsLogger.mTestableDeviceNames.get("fordf150").intValue());
+
+ mTestableMetricsLogger
+ .logSanitizedBluetoothDeviceName("BOSE QC_35 Noise Cancelling Headsets");
+ Assert.assertEquals(1,
+ mTestableMetricsLogger.mTestableDeviceNames.get("boseqc35").intValue());
+
+ mTestableMetricsLogger
+ .logSanitizedBluetoothDeviceName("BOSE Quiet Comfort 35 Headsets");
+ Assert.assertEquals(1,
+ mTestableMetricsLogger.mTestableDeviceNames.get("bosequietcomfort35").intValue());
+
+ mTestableMetricsLogger
+ .logSanitizedBluetoothDeviceName("Fitbit versa 3 band");
+ Assert.assertEquals(1,
+ mTestableMetricsLogger.mTestableDeviceNames.get("fitbitversa3").intValue());
+
+ mTestableMetricsLogger
+ .logSanitizedBluetoothDeviceName("vw atlas");
+ Assert.assertEquals(1,
+ mTestableMetricsLogger.mTestableDeviceNames.get("vwatlas").intValue());
+
+ mTestableMetricsLogger
+ .logSanitizedBluetoothDeviceName("My volkswagen tiguan");
+ Assert.assertEquals(1,
+ mTestableMetricsLogger.mTestableDeviceNames.get("volkswagentiguan").intValue());
+
+ mTestableMetricsLogger.mTestableDeviceNames.clear();
+ mTestableMetricsLogger.logSanitizedBluetoothDeviceName("");
+ Assert.assertTrue(mTestableMetricsLogger.mTestableDeviceNames.isEmpty());
+
+ mTestableMetricsLogger.logSanitizedBluetoothDeviceName(" ");
+ Assert.assertTrue(mTestableMetricsLogger.mTestableDeviceNames.isEmpty());
+
+ mTestableMetricsLogger.logSanitizedBluetoothDeviceName("weirddevice");
+ Assert.assertTrue(mTestableMetricsLogger.mTestableDeviceNames.isEmpty());
+
+ mTestableMetricsLogger.logSanitizedBluetoothDeviceName(""
+ + "My BOSE Quiet Comfort 35 Noise Cancelling Headsets");
+ // Too long, won't process
+ Assert.assertTrue(mTestableMetricsLogger.mTestableDeviceNames.isEmpty());
+
+ }
+ private void initTestingBloomfitler() {
+ byte[] bloomfilterData = DeviceBloomfilterGenerator.hexStringToByteArray(
+ DeviceBloomfilterGenerator.BLOOM_FILTER_DEFAULT);
+ try {
+ mTestableMetricsLogger.setBloomfilter(
+ BloomFilter.readFrom(
+ new ByteArrayInputStream(bloomfilterData), Funnels.byteArrayFunnel()));
+ } catch (IOException e) {
+ Assert.assertTrue(false);
+ }
+ }
}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/btservice/RemoteDevicesTest.java b/android/app/tests/unit/src/com/android/bluetooth/btservice/RemoteDevicesTest.java
index 42e93e5..86875f2 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/btservice/RemoteDevicesTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/btservice/RemoteDevicesTest.java
@@ -10,6 +10,7 @@
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothHeadsetClient;
import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothSinkAudioPolicy;
import android.content.Intent;
import android.os.Bundle;
import android.os.HandlerThread;
@@ -21,6 +22,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.bluetooth.Utils;
+import com.android.bluetooth.btservice.RemoteDevices.DeviceProperties;
import com.android.bluetooth.hfp.HeadsetHalConstants;
import org.junit.After;
@@ -517,6 +519,26 @@
Assert.assertNull(mRemoteDevices.getDeviceProperties(mDevice1));
}
+ @Test
+ public void testSetgetHfAudioPolicyForRemoteAg() {
+ // Verify that device property is null initially
+ Assert.assertNull(mRemoteDevices.getDeviceProperties(mDevice1));
+
+ mRemoteDevices.addDeviceProperties(Utils.getBytesFromAddress(TEST_BT_ADDR_1));
+
+ DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(mDevice1);
+ BluetoothSinkAudioPolicy policies = new BluetoothSinkAudioPolicy.Builder()
+ .setCallEstablishPolicy(BluetoothSinkAudioPolicy.POLICY_ALLOWED)
+ .setActiveDevicePolicyAfterConnection(BluetoothSinkAudioPolicy.POLICY_ALLOWED)
+ .setInBandRingtonePolicy(BluetoothSinkAudioPolicy.POLICY_ALLOWED)
+ .build();
+ deviceProp.setHfAudioPolicyForRemoteAg(policies);
+
+ // Verify that the audio policy properties are set and get propperly
+ Assert.assertEquals(policies, mRemoteDevices.getDeviceProperties(mDevice1)
+ .getHfAudioPolicyForRemoteAg());
+ }
+
private static void verifyBatteryLevelChangedIntent(BluetoothDevice device, int batteryLevel,
ArgumentCaptor<Intent> intentArgument) {
verifyBatteryLevelChangedIntent(device, batteryLevel, intentArgument.getValue());
diff --git a/android/app/tests/unit/src/com/android/bluetooth/btservice/storage/DatabaseManagerTest.java b/android/app/tests/unit/src/com/android/bluetooth/btservice/storage/DatabaseManagerTest.java
index 28357b6..cd306f4 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/btservice/storage/DatabaseManagerTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/btservice/storage/DatabaseManagerTest.java
@@ -30,6 +30,7 @@
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothSinkAudioPolicy;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
@@ -387,6 +388,10 @@
value, true);
testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_LE_AUDIO,
value, true);
+ testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_GMCS_CCCD,
+ value, true);
+ testSetGetCustomMetaCase(false, BluetoothDevice.METADATA_GTBS_CCCD,
+ value, true);
testSetGetCustomMetaCase(false, badKey, value, false);
// Device is in database
@@ -447,6 +452,24 @@
value, true);
testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_LE_AUDIO,
value, true);
+ testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_GMCS_CCCD,
+ value, true);
+ testSetGetCustomMetaCase(true, BluetoothDevice.METADATA_GTBS_CCCD,
+ value, true);
+ }
+ @Test
+ public void testSetGetAudioPolicyMetaData() {
+ int badKey = 100;
+ BluetoothSinkAudioPolicy value = new BluetoothSinkAudioPolicy.Builder()
+ .setCallEstablishPolicy(BluetoothSinkAudioPolicy.POLICY_ALLOWED)
+ .setActiveDevicePolicyAfterConnection(BluetoothSinkAudioPolicy.POLICY_NOT_ALLOWED)
+ .setInBandRingtonePolicy(BluetoothSinkAudioPolicy.POLICY_ALLOWED)
+ .build();
+
+ // Device is not in database
+ testSetGetAudioPolicyMetadataCase(false, value, true);
+ // Device is in database
+ testSetGetAudioPolicyMetadataCase(true, value, true);
}
@Test
@@ -1143,7 +1166,7 @@
@Test
public void testDatabaseMigration_111_112() throws IOException {
String testString = "TEST STRING";
- // Create a database with version 109
+ // Create a database with version 111
SupportSQLiteDatabase db = testHelper.createDatabase(DB_NAME, 111);
// insert a device to the database
ContentValues device = new ContentValues();
@@ -1189,7 +1212,7 @@
@Test
public void testDatabaseMigration_113_114() throws IOException {
- // Create a database with version 112
+ // Create a database with version 113
SupportSQLiteDatabase db = testHelper.createDatabase(DB_NAME, 113);
// insert a device to the database
ContentValues device = new ContentValues();
@@ -1209,6 +1232,83 @@
}
}
+ @Test
+ public void testDatabaseMigration_114_115() throws IOException {
+ // Create a database with version 114
+ SupportSQLiteDatabase db = testHelper.createDatabase(DB_NAME, 114);
+ // insert a device to the database
+ ContentValues device = new ContentValues();
+ device.put("address", TEST_BT_ADDR);
+ device.put("migrated", false);
+ assertThat(db.insert("metadata", SQLiteDatabase.CONFLICT_IGNORE, device),
+ CoreMatchers.not(-1));
+
+ // Migrate database from 114 to 115
+ db.close();
+ db = testHelper.runMigrationsAndValidate(DB_NAME, 115, true,
+ MetadataDatabase.MIGRATION_114_115);
+ Cursor cursor = db.query("SELECT * FROM metadata");
+
+ assertHasColumn(cursor, "call_establish_audio_policy", true);
+ assertHasColumn(cursor, "connecting_time_audio_policy", true);
+ assertHasColumn(cursor, "in_band_ringtone_audio_policy", true);
+ while (cursor.moveToNext()) {
+ // Check the new columns was added with default value
+ assertColumnBlobData(cursor, "call_establish_audio_policy", null);
+ assertColumnBlobData(cursor, "connecting_time_audio_policy", null);
+ assertColumnBlobData(cursor, "in_band_ringtone_audio_policy", null);
+ }
+ }
+
+ @Test
+ public void testDatabaseMigration_115_116() throws IOException {
+ // Create a database with version 115
+ SupportSQLiteDatabase db = testHelper.createDatabase(DB_NAME, 115);
+ // insert a device to the database
+ ContentValues device = new ContentValues();
+ device.put("address", TEST_BT_ADDR);
+ device.put("migrated", false);
+ assertThat(db.insert("metadata", SQLiteDatabase.CONFLICT_IGNORE, device),
+ CoreMatchers.not(-1));
+
+ // Migrate database from 115 to 116
+ db.close();
+ db = testHelper.runMigrationsAndValidate(DB_NAME, 116, true,
+ MetadataDatabase.MIGRATION_115_116);
+ Cursor cursor = db.query("SELECT * FROM metadata");
+ assertHasColumn(cursor, "preferred_output_only_profile", true);
+ assertHasColumn(cursor, "preferred_duplex_profile", true);
+ while (cursor.moveToNext()) {
+ // Check the new columns was added with default value
+ assertColumnIntData(cursor, "preferred_output_only_profile", 0);
+ assertColumnIntData(cursor, "preferred_duplex_profile", 0);
+ }
+ }
+
+ @Test
+ public void testDatabaseMigration_116_117() throws IOException {
+ // Create a database with version 116
+ SupportSQLiteDatabase db = testHelper.createDatabase(DB_NAME, 116);
+ // insert a device to the database
+ ContentValues device = new ContentValues();
+ device.put("address", TEST_BT_ADDR);
+ device.put("migrated", false);
+ assertThat(db.insert("metadata", SQLiteDatabase.CONFLICT_IGNORE, device),
+ CoreMatchers.not(-1));
+ // Migrate database from 116 to 117
+ db.close();
+ db = testHelper.runMigrationsAndValidate(DB_NAME, 117, true,
+ MetadataDatabase.MIGRATION_116_117);
+ Cursor cursor = db.query("SELECT * FROM metadata");
+ assertHasColumn(cursor, "gmcs_cccd", true);
+ assertHasColumn(cursor, "gtbs_cccd", true);
+ while (cursor.moveToNext()) {
+ // Check the new columns was added with default value
+ assertColumnBlobData(cursor, "gmcs_cccd", null);
+ assertColumnBlobData(cursor, "gtbs_cccd", null);
+ }
+ }
+
/**
* Helper function to check whether the database has the expected column
*/
@@ -1379,4 +1479,38 @@
// Wait for clear database
TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
}
+
+ void testSetGetAudioPolicyMetadataCase(boolean stored,
+ BluetoothSinkAudioPolicy policy, boolean expectedResult) {
+ BluetoothSinkAudioPolicy testPolicy = new BluetoothSinkAudioPolicy.Builder().build();
+ if (stored) {
+ Metadata data = new Metadata(TEST_BT_ADDR);
+ mDatabaseManager.mMetadataCache.put(TEST_BT_ADDR, data);
+ mDatabase.insert(data);
+ Assert.assertEquals(expectedResult,
+ mDatabaseManager.setAudioPolicyMetadata(mTestDevice, testPolicy));
+ }
+ Assert.assertEquals(expectedResult,
+ mDatabaseManager.setAudioPolicyMetadata(mTestDevice, policy));
+ if (expectedResult) {
+ // Check for callback and get value
+ Assert.assertEquals(policy,
+ mDatabaseManager.getAudioPolicyMetadata(mTestDevice));
+ } else {
+ Assert.assertNull(mDatabaseManager.getAudioPolicyMetadata(mTestDevice));
+ return;
+ }
+ // Wait for database update
+ TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
+
+ // Check whether the value is saved in database
+ restartDatabaseManagerHelper();
+ Assert.assertEquals(policy,
+ mDatabaseManager.getAudioPolicyMetadata(mTestDevice));
+
+ mDatabaseManager.factoryReset();
+ mDatabaseManager.mMetadataCache.clear();
+ // Wait for clear database
+ TestUtils.waitForLooperToFinishScheduledTask(mDatabaseManager.getHandlerLooper());
+ }
}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/btservice/storage/schemas/com.android.bluetooth.btservice.storage.MetadataDatabase/115.json b/android/app/tests/unit/src/com/android/bluetooth/btservice/storage/schemas/com.android.bluetooth.btservice.storage.MetadataDatabase/115.json
new file mode 100644
index 0000000..5d576dc
--- /dev/null
+++ b/android/app/tests/unit/src/com/android/bluetooth/btservice/storage/schemas/com.android.bluetooth.btservice.storage.MetadataDatabase/115.json
@@ -0,0 +1,358 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 115,
+ "identityHash": "c61976c8f6248cefd19ef8f25f543e01",
+ "entities": [
+ {
+ "tableName": "metadata",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`address` TEXT NOT NULL, `migrated` INTEGER NOT NULL, `a2dpSupportsOptionalCodecs` INTEGER NOT NULL, `a2dpOptionalCodecsEnabled` INTEGER NOT NULL, `last_active_time` INTEGER NOT NULL, `is_active_a2dp_device` INTEGER NOT NULL, `a2dp_connection_policy` INTEGER, `a2dp_sink_connection_policy` INTEGER, `hfp_connection_policy` INTEGER, `hfp_client_connection_policy` INTEGER, `hid_host_connection_policy` INTEGER, `pan_connection_policy` INTEGER, `pbap_connection_policy` INTEGER, `pbap_client_connection_policy` INTEGER, `map_connection_policy` INTEGER, `sap_connection_policy` INTEGER, `hearing_aid_connection_policy` INTEGER, `hap_client_connection_policy` INTEGER, `map_client_connection_policy` INTEGER, `le_audio_connection_policy` INTEGER, `volume_control_connection_policy` INTEGER, `csip_set_coordinator_connection_policy` INTEGER, `le_call_control_connection_policy` INTEGER, `bass_client_connection_policy` INTEGER, `battery_connection_policy` INTEGER, `manufacturer_name` BLOB, `model_name` BLOB, `software_version` BLOB, `hardware_version` BLOB, `companion_app` BLOB, `main_icon` BLOB, `is_untethered_headset` BLOB, `untethered_left_icon` BLOB, `untethered_right_icon` BLOB, `untethered_case_icon` BLOB, `untethered_left_battery` BLOB, `untethered_right_battery` BLOB, `untethered_case_battery` BLOB, `untethered_left_charging` BLOB, `untethered_right_charging` BLOB, `untethered_case_charging` BLOB, `enhanced_settings_ui_uri` BLOB, `device_type` BLOB, `main_battery` BLOB, `main_charging` BLOB, `main_low_battery_threshold` BLOB, `untethered_left_low_battery_threshold` BLOB, `untethered_right_low_battery_threshold` BLOB, `untethered_case_low_battery_threshold` BLOB, `spatial_audio` BLOB, `fastpair_customized` BLOB, `le_audio` BLOB, `call_establish_audio_policy` INTEGER, `connecting_time_audio_policy` INTEGER, `in_band_ringtone_audio_policy` INTEGER, PRIMARY KEY(`address`))",
+ "fields": [
+ {
+ "fieldPath": "address",
+ "columnName": "address",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "migrated",
+ "columnName": "migrated",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "a2dpSupportsOptionalCodecs",
+ "columnName": "a2dpSupportsOptionalCodecs",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "a2dpOptionalCodecsEnabled",
+ "columnName": "a2dpOptionalCodecsEnabled",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "last_active_time",
+ "columnName": "last_active_time",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "is_active_a2dp_device",
+ "columnName": "is_active_a2dp_device",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.a2dp_connection_policy",
+ "columnName": "a2dp_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.a2dp_sink_connection_policy",
+ "columnName": "a2dp_sink_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.hfp_connection_policy",
+ "columnName": "hfp_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.hfp_client_connection_policy",
+ "columnName": "hfp_client_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.hid_host_connection_policy",
+ "columnName": "hid_host_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.pan_connection_policy",
+ "columnName": "pan_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.pbap_connection_policy",
+ "columnName": "pbap_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.pbap_client_connection_policy",
+ "columnName": "pbap_client_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.map_connection_policy",
+ "columnName": "map_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.sap_connection_policy",
+ "columnName": "sap_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.hearing_aid_connection_policy",
+ "columnName": "hearing_aid_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.hap_client_connection_policy",
+ "columnName": "hap_client_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.map_client_connection_policy",
+ "columnName": "map_client_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.le_audio_connection_policy",
+ "columnName": "le_audio_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.volume_control_connection_policy",
+ "columnName": "volume_control_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.csip_set_coordinator_connection_policy",
+ "columnName": "csip_set_coordinator_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.le_call_control_connection_policy",
+ "columnName": "le_call_control_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.bass_client_connection_policy",
+ "columnName": "bass_client_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.battery_connection_policy",
+ "columnName": "battery_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.manufacturer_name",
+ "columnName": "manufacturer_name",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.model_name",
+ "columnName": "model_name",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.software_version",
+ "columnName": "software_version",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.hardware_version",
+ "columnName": "hardware_version",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.companion_app",
+ "columnName": "companion_app",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.main_icon",
+ "columnName": "main_icon",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.is_untethered_headset",
+ "columnName": "is_untethered_headset",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.untethered_left_icon",
+ "columnName": "untethered_left_icon",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.untethered_right_icon",
+ "columnName": "untethered_right_icon",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.untethered_case_icon",
+ "columnName": "untethered_case_icon",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.untethered_left_battery",
+ "columnName": "untethered_left_battery",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.untethered_right_battery",
+ "columnName": "untethered_right_battery",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.untethered_case_battery",
+ "columnName": "untethered_case_battery",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.untethered_left_charging",
+ "columnName": "untethered_left_charging",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.untethered_right_charging",
+ "columnName": "untethered_right_charging",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.untethered_case_charging",
+ "columnName": "untethered_case_charging",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.enhanced_settings_ui_uri",
+ "columnName": "enhanced_settings_ui_uri",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.device_type",
+ "columnName": "device_type",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.main_battery",
+ "columnName": "main_battery",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.main_charging",
+ "columnName": "main_charging",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.main_low_battery_threshold",
+ "columnName": "main_low_battery_threshold",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.untethered_left_low_battery_threshold",
+ "columnName": "untethered_left_low_battery_threshold",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.untethered_right_low_battery_threshold",
+ "columnName": "untethered_right_low_battery_threshold",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.untethered_case_low_battery_threshold",
+ "columnName": "untethered_case_low_battery_threshold",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.spatial_audio",
+ "columnName": "spatial_audio",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.fastpair_customized",
+ "columnName": "fastpair_customized",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.le_audio",
+ "columnName": "le_audio",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "audioPolicyMetadata.callEstablishAudioPolicy",
+ "columnName": "call_establish_audio_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "audioPolicyMetadata.connectingTimeAudioPolicy",
+ "columnName": "connecting_time_audio_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "audioPolicyMetadata.inBandRingtoneAudioPolicy",
+ "columnName": "in_band_ringtone_audio_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "address"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ }
+ ],
+ "views": [],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'c61976c8f6248cefd19ef8f25f543e01')"
+ ]
+ }
+}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/btservice/storage/schemas/com.android.bluetooth.btservice.storage.MetadataDatabase/116.json b/android/app/tests/unit/src/com/android/bluetooth/btservice/storage/schemas/com.android.bluetooth.btservice.storage.MetadataDatabase/116.json
new file mode 100644
index 0000000..2e85b51
--- /dev/null
+++ b/android/app/tests/unit/src/com/android/bluetooth/btservice/storage/schemas/com.android.bluetooth.btservice.storage.MetadataDatabase/116.json
@@ -0,0 +1,370 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 116,
+ "identityHash": "0b8549de3acad8b14fe6f7198206ea02",
+ "entities": [
+ {
+ "tableName": "metadata",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`address` TEXT NOT NULL, `migrated` INTEGER NOT NULL, `a2dpSupportsOptionalCodecs` INTEGER NOT NULL, `a2dpOptionalCodecsEnabled` INTEGER NOT NULL, `last_active_time` INTEGER NOT NULL, `is_active_a2dp_device` INTEGER NOT NULL, `preferred_output_only_profile` INTEGER NOT NULL, `preferred_duplex_profile` INTEGER NOT NULL, `a2dp_connection_policy` INTEGER, `a2dp_sink_connection_policy` INTEGER, `hfp_connection_policy` INTEGER, `hfp_client_connection_policy` INTEGER, `hid_host_connection_policy` INTEGER, `pan_connection_policy` INTEGER, `pbap_connection_policy` INTEGER, `pbap_client_connection_policy` INTEGER, `map_connection_policy` INTEGER, `sap_connection_policy` INTEGER, `hearing_aid_connection_policy` INTEGER, `hap_client_connection_policy` INTEGER, `map_client_connection_policy` INTEGER, `le_audio_connection_policy` INTEGER, `volume_control_connection_policy` INTEGER, `csip_set_coordinator_connection_policy` INTEGER, `le_call_control_connection_policy` INTEGER, `bass_client_connection_policy` INTEGER, `battery_connection_policy` INTEGER, `manufacturer_name` BLOB, `model_name` BLOB, `software_version` BLOB, `hardware_version` BLOB, `companion_app` BLOB, `main_icon` BLOB, `is_untethered_headset` BLOB, `untethered_left_icon` BLOB, `untethered_right_icon` BLOB, `untethered_case_icon` BLOB, `untethered_left_battery` BLOB, `untethered_right_battery` BLOB, `untethered_case_battery` BLOB, `untethered_left_charging` BLOB, `untethered_right_charging` BLOB, `untethered_case_charging` BLOB, `enhanced_settings_ui_uri` BLOB, `device_type` BLOB, `main_battery` BLOB, `main_charging` BLOB, `main_low_battery_threshold` BLOB, `untethered_left_low_battery_threshold` BLOB, `untethered_right_low_battery_threshold` BLOB, `untethered_case_low_battery_threshold` BLOB, `spatial_audio` BLOB, `fastpair_customized` BLOB, `le_audio` BLOB, `call_establish_audio_policy` INTEGER, `connecting_time_audio_policy` INTEGER, `in_band_ringtone_audio_policy` INTEGER, PRIMARY KEY(`address`))",
+ "fields": [
+ {
+ "fieldPath": "address",
+ "columnName": "address",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "migrated",
+ "columnName": "migrated",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "a2dpSupportsOptionalCodecs",
+ "columnName": "a2dpSupportsOptionalCodecs",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "a2dpOptionalCodecsEnabled",
+ "columnName": "a2dpOptionalCodecsEnabled",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "last_active_time",
+ "columnName": "last_active_time",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "is_active_a2dp_device",
+ "columnName": "is_active_a2dp_device",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "preferred_output_only_profile",
+ "columnName": "preferred_output_only_profile",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "preferred_duplex_profile",
+ "columnName": "preferred_duplex_profile",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.a2dp_connection_policy",
+ "columnName": "a2dp_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.a2dp_sink_connection_policy",
+ "columnName": "a2dp_sink_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.hfp_connection_policy",
+ "columnName": "hfp_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.hfp_client_connection_policy",
+ "columnName": "hfp_client_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.hid_host_connection_policy",
+ "columnName": "hid_host_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.pan_connection_policy",
+ "columnName": "pan_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.pbap_connection_policy",
+ "columnName": "pbap_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.pbap_client_connection_policy",
+ "columnName": "pbap_client_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.map_connection_policy",
+ "columnName": "map_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.sap_connection_policy",
+ "columnName": "sap_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.hearing_aid_connection_policy",
+ "columnName": "hearing_aid_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.hap_client_connection_policy",
+ "columnName": "hap_client_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.map_client_connection_policy",
+ "columnName": "map_client_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.le_audio_connection_policy",
+ "columnName": "le_audio_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.volume_control_connection_policy",
+ "columnName": "volume_control_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.csip_set_coordinator_connection_policy",
+ "columnName": "csip_set_coordinator_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.le_call_control_connection_policy",
+ "columnName": "le_call_control_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.bass_client_connection_policy",
+ "columnName": "bass_client_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.battery_connection_policy",
+ "columnName": "battery_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.manufacturer_name",
+ "columnName": "manufacturer_name",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.model_name",
+ "columnName": "model_name",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.software_version",
+ "columnName": "software_version",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.hardware_version",
+ "columnName": "hardware_version",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.companion_app",
+ "columnName": "companion_app",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.main_icon",
+ "columnName": "main_icon",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.is_untethered_headset",
+ "columnName": "is_untethered_headset",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.untethered_left_icon",
+ "columnName": "untethered_left_icon",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.untethered_right_icon",
+ "columnName": "untethered_right_icon",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.untethered_case_icon",
+ "columnName": "untethered_case_icon",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.untethered_left_battery",
+ "columnName": "untethered_left_battery",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.untethered_right_battery",
+ "columnName": "untethered_right_battery",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.untethered_case_battery",
+ "columnName": "untethered_case_battery",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.untethered_left_charging",
+ "columnName": "untethered_left_charging",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.untethered_right_charging",
+ "columnName": "untethered_right_charging",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.untethered_case_charging",
+ "columnName": "untethered_case_charging",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.enhanced_settings_ui_uri",
+ "columnName": "enhanced_settings_ui_uri",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.device_type",
+ "columnName": "device_type",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.main_battery",
+ "columnName": "main_battery",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.main_charging",
+ "columnName": "main_charging",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.main_low_battery_threshold",
+ "columnName": "main_low_battery_threshold",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.untethered_left_low_battery_threshold",
+ "columnName": "untethered_left_low_battery_threshold",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.untethered_right_low_battery_threshold",
+ "columnName": "untethered_right_low_battery_threshold",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.untethered_case_low_battery_threshold",
+ "columnName": "untethered_case_low_battery_threshold",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.spatial_audio",
+ "columnName": "spatial_audio",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.fastpair_customized",
+ "columnName": "fastpair_customized",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.le_audio",
+ "columnName": "le_audio",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "audioPolicyMetadata.callEstablishAudioPolicy",
+ "columnName": "call_establish_audio_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "audioPolicyMetadata.connectingTimeAudioPolicy",
+ "columnName": "connecting_time_audio_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "audioPolicyMetadata.inBandRingtoneAudioPolicy",
+ "columnName": "in_band_ringtone_audio_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "address"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ }
+ ],
+ "views": [],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '0b8549de3acad8b14fe6f7198206ea02')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/android/app/tests/unit/src/com/android/bluetooth/btservice/storage/schemas/com.android.bluetooth.btservice.storage.MetadataDatabase/117.json b/android/app/tests/unit/src/com/android/bluetooth/btservice/storage/schemas/com.android.bluetooth.btservice.storage.MetadataDatabase/117.json
new file mode 100644
index 0000000..d4c1f7e
--- /dev/null
+++ b/android/app/tests/unit/src/com/android/bluetooth/btservice/storage/schemas/com.android.bluetooth.btservice.storage.MetadataDatabase/117.json
@@ -0,0 +1,382 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 117,
+ "identityHash": "b3363a857e6d4f3ece8ba92d57d52c26",
+ "entities": [
+ {
+ "tableName": "metadata",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`address` TEXT NOT NULL, `migrated` INTEGER NOT NULL, `a2dpSupportsOptionalCodecs` INTEGER NOT NULL, `a2dpOptionalCodecsEnabled` INTEGER NOT NULL, `last_active_time` INTEGER NOT NULL, `is_active_a2dp_device` INTEGER NOT NULL, `preferred_output_only_profile` INTEGER NOT NULL, `preferred_duplex_profile` INTEGER NOT NULL, `a2dp_connection_policy` INTEGER, `a2dp_sink_connection_policy` INTEGER, `hfp_connection_policy` INTEGER, `hfp_client_connection_policy` INTEGER, `hid_host_connection_policy` INTEGER, `pan_connection_policy` INTEGER, `pbap_connection_policy` INTEGER, `pbap_client_connection_policy` INTEGER, `map_connection_policy` INTEGER, `sap_connection_policy` INTEGER, `hearing_aid_connection_policy` INTEGER, `hap_client_connection_policy` INTEGER, `map_client_connection_policy` INTEGER, `le_audio_connection_policy` INTEGER, `volume_control_connection_policy` INTEGER, `csip_set_coordinator_connection_policy` INTEGER, `le_call_control_connection_policy` INTEGER, `bass_client_connection_policy` INTEGER, `battery_connection_policy` INTEGER, `manufacturer_name` BLOB, `model_name` BLOB, `software_version` BLOB, `hardware_version` BLOB, `companion_app` BLOB, `main_icon` BLOB, `is_untethered_headset` BLOB, `untethered_left_icon` BLOB, `untethered_right_icon` BLOB, `untethered_case_icon` BLOB, `untethered_left_battery` BLOB, `untethered_right_battery` BLOB, `untethered_case_battery` BLOB, `untethered_left_charging` BLOB, `untethered_right_charging` BLOB, `untethered_case_charging` BLOB, `enhanced_settings_ui_uri` BLOB, `device_type` BLOB, `main_battery` BLOB, `main_charging` BLOB, `main_low_battery_threshold` BLOB, `untethered_left_low_battery_threshold` BLOB, `untethered_right_low_battery_threshold` BLOB, `untethered_case_low_battery_threshold` BLOB, `spatial_audio` BLOB, `fastpair_customized` BLOB, `le_audio` BLOB, `gmcs_cccd` BLOB, `gtbs_cccd` BLOB, `call_establish_audio_policy` INTEGER, `connecting_time_audio_policy` INTEGER, `in_band_ringtone_audio_policy` INTEGER, PRIMARY KEY(`address`))",
+ "fields": [
+ {
+ "fieldPath": "address",
+ "columnName": "address",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "migrated",
+ "columnName": "migrated",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "a2dpSupportsOptionalCodecs",
+ "columnName": "a2dpSupportsOptionalCodecs",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "a2dpOptionalCodecsEnabled",
+ "columnName": "a2dpOptionalCodecsEnabled",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "last_active_time",
+ "columnName": "last_active_time",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "is_active_a2dp_device",
+ "columnName": "is_active_a2dp_device",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "preferred_output_only_profile",
+ "columnName": "preferred_output_only_profile",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "preferred_duplex_profile",
+ "columnName": "preferred_duplex_profile",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.a2dp_connection_policy",
+ "columnName": "a2dp_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.a2dp_sink_connection_policy",
+ "columnName": "a2dp_sink_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.hfp_connection_policy",
+ "columnName": "hfp_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.hfp_client_connection_policy",
+ "columnName": "hfp_client_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.hid_host_connection_policy",
+ "columnName": "hid_host_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.pan_connection_policy",
+ "columnName": "pan_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.pbap_connection_policy",
+ "columnName": "pbap_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.pbap_client_connection_policy",
+ "columnName": "pbap_client_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.map_connection_policy",
+ "columnName": "map_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.sap_connection_policy",
+ "columnName": "sap_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.hearing_aid_connection_policy",
+ "columnName": "hearing_aid_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.hap_client_connection_policy",
+ "columnName": "hap_client_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.map_client_connection_policy",
+ "columnName": "map_client_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.le_audio_connection_policy",
+ "columnName": "le_audio_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.volume_control_connection_policy",
+ "columnName": "volume_control_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.csip_set_coordinator_connection_policy",
+ "columnName": "csip_set_coordinator_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.le_call_control_connection_policy",
+ "columnName": "le_call_control_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.bass_client_connection_policy",
+ "columnName": "bass_client_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "profileConnectionPolicies.battery_connection_policy",
+ "columnName": "battery_connection_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.manufacturer_name",
+ "columnName": "manufacturer_name",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.model_name",
+ "columnName": "model_name",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.software_version",
+ "columnName": "software_version",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.hardware_version",
+ "columnName": "hardware_version",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.companion_app",
+ "columnName": "companion_app",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.main_icon",
+ "columnName": "main_icon",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.is_untethered_headset",
+ "columnName": "is_untethered_headset",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.untethered_left_icon",
+ "columnName": "untethered_left_icon",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.untethered_right_icon",
+ "columnName": "untethered_right_icon",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.untethered_case_icon",
+ "columnName": "untethered_case_icon",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.untethered_left_battery",
+ "columnName": "untethered_left_battery",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.untethered_right_battery",
+ "columnName": "untethered_right_battery",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.untethered_case_battery",
+ "columnName": "untethered_case_battery",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.untethered_left_charging",
+ "columnName": "untethered_left_charging",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.untethered_right_charging",
+ "columnName": "untethered_right_charging",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.untethered_case_charging",
+ "columnName": "untethered_case_charging",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.enhanced_settings_ui_uri",
+ "columnName": "enhanced_settings_ui_uri",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.device_type",
+ "columnName": "device_type",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.main_battery",
+ "columnName": "main_battery",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.main_charging",
+ "columnName": "main_charging",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.main_low_battery_threshold",
+ "columnName": "main_low_battery_threshold",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.untethered_left_low_battery_threshold",
+ "columnName": "untethered_left_low_battery_threshold",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.untethered_right_low_battery_threshold",
+ "columnName": "untethered_right_low_battery_threshold",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.untethered_case_low_battery_threshold",
+ "columnName": "untethered_case_low_battery_threshold",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.spatial_audio",
+ "columnName": "spatial_audio",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.fastpair_customized",
+ "columnName": "fastpair_customized",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.le_audio",
+ "columnName": "le_audio",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.gmcs_cccd",
+ "columnName": "gmcs_cccd",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "publicMetadata.gtbs_cccd",
+ "columnName": "gtbs_cccd",
+ "affinity": "BLOB",
+ "notNull": false
+ },
+ {
+ "fieldPath": "audioPolicyMetadata.callEstablishAudioPolicy",
+ "columnName": "call_establish_audio_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "audioPolicyMetadata.connectingTimeAudioPolicy",
+ "columnName": "connecting_time_audio_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "audioPolicyMetadata.inBandRingtoneAudioPolicy",
+ "columnName": "in_band_ringtone_audio_policy",
+ "affinity": "INTEGER",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "address"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ }
+ ],
+ "views": [],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'b3363a857e6d4f3ece8ba92d57d52c26')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/android/app/tests/unit/src/com/android/bluetooth/csip/CsipSetCoordinatorStateMachineTest.java b/android/app/tests/unit/src/com/android/bluetooth/csip/CsipSetCoordinatorStateMachineTest.java
index b2d99f3..8ae5f93 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/csip/CsipSetCoordinatorStateMachineTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/csip/CsipSetCoordinatorStateMachineTest.java
@@ -17,6 +17,11 @@
package com.android.bluetooth.csip;
+import static android.bluetooth.BluetoothProfile.STATE_CONNECTED;
+import static android.bluetooth.BluetoothProfile.STATE_CONNECTING;
+import static android.bluetooth.BluetoothProfile.STATE_DISCONNECTED;
+import static android.bluetooth.BluetoothProfile.STATE_DISCONNECTING;
+
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
@@ -25,9 +30,10 @@
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
-import android.content.Context;
import android.content.Intent;
import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
import android.test.suitebuilder.annotation.MediumTest;
import androidx.test.InstrumentationRegistry;
@@ -42,6 +48,7 @@
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
@MediumTest
@@ -49,11 +56,10 @@
public class CsipSetCoordinatorStateMachineTest {
private final String mFlagDexmarker = System.getProperty("dexmaker.share_classloader", "false");
- private Context mTargetContext;
private BluetoothAdapter mAdapter;
private BluetoothDevice mTestDevice;
private HandlerThread mHandlerThread;
- private CsipSetCoordinatorStateMachine mStateMachine;
+ private CsipSetCoordinatorStateMachineWrapper mStateMachine;
private static final int TIMEOUT_MS = 1000;
@Mock private AdapterService mAdapterService;
@@ -66,7 +72,6 @@
System.setProperty("dexmaker.share_classloader", "true");
}
- mTargetContext = InstrumentationRegistry.getTargetContext();
// Set up mocks and test assets
MockitoAnnotations.initMocks(this);
TestUtils.setAdapterService(mAdapterService);
@@ -79,8 +84,8 @@
// Set up thread and looper
mHandlerThread = new HandlerThread("CsipSetCoordinatorServiceTestHandlerThread");
mHandlerThread.start();
- mStateMachine = new CsipSetCoordinatorStateMachine(
- mTestDevice, mService, mNativeInterface, mHandlerThread.getLooper());
+ mStateMachine = spy(new CsipSetCoordinatorStateMachineWrapper(
+ mTestDevice, mService, mNativeInterface, mHandlerThread.getLooper()));
// Override the timeout value to speed up the test
CsipSetCoordinatorStateMachine.sConnectTimeoutMs = 1000;
@@ -103,7 +108,7 @@
@Test
public void testDefaultDisconnectedState() {
Assert.assertEquals(
- BluetoothProfile.STATE_DISCONNECTED, mStateMachine.getConnectionState());
+ STATE_DISCONNECTED, mStateMachine.getConnectionState());
}
/**
@@ -155,7 +160,7 @@
ArgumentCaptor<Intent> intentArgument1 = ArgumentCaptor.forClass(Intent.class);
verify(mService, timeout(TIMEOUT_MS).times(1))
.sendBroadcast(intentArgument1.capture(), anyString());
- Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
+ Assert.assertEquals(STATE_CONNECTING,
intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
// Check that we are in Connecting state
@@ -196,7 +201,7 @@
ArgumentCaptor<Intent> intentArgument1 = ArgumentCaptor.forClass(Intent.class);
verify(mService, timeout(TIMEOUT_MS).times(1))
.sendBroadcast(intentArgument1.capture(), anyString());
- Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
+ Assert.assertEquals(STATE_CONNECTING,
intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
// Check that we are in Connecting state
@@ -207,7 +212,7 @@
ArgumentCaptor<Intent> intentArgument2 = ArgumentCaptor.forClass(Intent.class);
verify(mService, timeout(CsipSetCoordinatorStateMachine.sConnectTimeoutMs * 2).times(2))
.sendBroadcast(intentArgument2.capture(), anyString());
- Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+ Assert.assertEquals(STATE_DISCONNECTED,
intentArgument2.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
// Check that we are in Disconnected state
@@ -236,7 +241,7 @@
ArgumentCaptor<Intent> intentArgument1 = ArgumentCaptor.forClass(Intent.class);
verify(mService, timeout(TIMEOUT_MS).times(1))
.sendBroadcast(intentArgument1.capture(), anyString());
- Assert.assertEquals(BluetoothProfile.STATE_CONNECTING,
+ Assert.assertEquals(STATE_CONNECTING,
intentArgument1.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
// Check that we are in Connecting state
@@ -247,7 +252,7 @@
ArgumentCaptor<Intent> intentArgument2 = ArgumentCaptor.forClass(Intent.class);
verify(mService, timeout(CsipSetCoordinatorStateMachine.sConnectTimeoutMs * 2).times(2))
.sendBroadcast(intentArgument2.capture(), anyString());
- Assert.assertEquals(BluetoothProfile.STATE_DISCONNECTED,
+ Assert.assertEquals(STATE_DISCONNECTED,
intentArgument2.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
// Check that we are in Disconnected state
@@ -255,4 +260,427 @@
IsInstanceOf.instanceOf(CsipSetCoordinatorStateMachine.Disconnected.class));
verify(mNativeInterface).disconnect(eq(mTestDevice));
}
+
+ @Test
+ public void testGetDevice() {
+ Assert.assertEquals(mTestDevice, mStateMachine.getDevice());
+ }
+
+ @Test
+ public void testIsConnected() {
+ Assert.assertFalse(mStateMachine.isConnected());
+
+ initToConnectedState();
+ Assert.assertTrue(mStateMachine.isConnected());
+ }
+
+ @Test
+ public void testDumpDoesNotCrash() {
+ mStateMachine.dump(new StringBuilder());
+ }
+
+ @Test
+ public void testProcessDisconnectMessage_onDisconnectedState() {
+ mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.DISCONNECT);
+ TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+ Assert.assertEquals(STATE_DISCONNECTED, mStateMachine.getConnectionState());
+ }
+
+ @Test
+ public void testProcessConnectMessage_onDisconnectedState() {
+ allowConnection(false);
+ mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.CONNECT);
+ TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+ Assert.assertEquals(STATE_DISCONNECTED, mStateMachine.getConnectionState());
+
+ allowConnection(false);
+ mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.CONNECT);
+ TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+ Assert.assertEquals(STATE_DISCONNECTED, mStateMachine.getConnectionState());
+
+ allowConnection(true);
+ doReturn(true).when(mNativeInterface).connect(any(BluetoothDevice.class));
+ sendMessageAndVerifyTransition(
+ mStateMachine.obtainMessage(CsipSetCoordinatorStateMachine.CONNECT),
+ CsipSetCoordinatorStateMachine.Connecting.class);
+ }
+
+ @Test
+ public void testStackEvent_withoutStateChange_onDisconnectedState() {
+ allowConnection(false);
+
+ CsipSetCoordinatorStackEvent event = new CsipSetCoordinatorStackEvent(-1);
+ mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event);
+ TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+ Assert.assertEquals(STATE_DISCONNECTED, mStateMachine.getConnectionState());
+
+ event = new CsipSetCoordinatorStackEvent(
+ CsipSetCoordinatorStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+ event.valueInt1 = CsipSetCoordinatorStackEvent.CONNECTION_STATE_DISCONNECTED;
+ mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event);
+ TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+ Assert.assertEquals(STATE_DISCONNECTED, mStateMachine.getConnectionState());
+
+ event = new CsipSetCoordinatorStackEvent(
+ CsipSetCoordinatorStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+ event.valueInt1 = CsipSetCoordinatorStackEvent.CONNECTION_STATE_CONNECTING;
+ mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event);
+ TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+ Assert.assertEquals(STATE_DISCONNECTED, mStateMachine.getConnectionState());
+ verify(mNativeInterface).disconnect(mTestDevice);
+
+ Mockito.clearInvocations(mNativeInterface);
+ event = new CsipSetCoordinatorStackEvent(
+ CsipSetCoordinatorStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+ event.valueInt1 = CsipSetCoordinatorStackEvent.CONNECTION_STATE_CONNECTED;
+ mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event);
+ TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+ Assert.assertEquals(STATE_DISCONNECTED, mStateMachine.getConnectionState());
+ verify(mNativeInterface).disconnect(mTestDevice);
+
+ event = new CsipSetCoordinatorStackEvent(
+ CsipSetCoordinatorStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+ event.valueInt1 = CsipSetCoordinatorStackEvent.CONNECTION_STATE_DISCONNECTING;
+ mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event);
+ TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+ Assert.assertEquals(STATE_DISCONNECTED, mStateMachine.getConnectionState());
+
+ event = new CsipSetCoordinatorStackEvent(
+ CsipSetCoordinatorStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+ event.valueInt1 = -1;
+ mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event);
+ TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+ Assert.assertEquals(STATE_DISCONNECTED, mStateMachine.getConnectionState());
+ }
+
+ @Test
+ public void testStackEvent_toConnectingState_onDisconnectedState() {
+ allowConnection(true);
+ CsipSetCoordinatorStackEvent event = new CsipSetCoordinatorStackEvent(
+ CsipSetCoordinatorStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+ event.valueInt1 = CsipSetCoordinatorStackEvent.CONNECTION_STATE_CONNECTING;
+ sendMessageAndVerifyTransition(
+ mStateMachine.obtainMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event),
+ CsipSetCoordinatorStateMachine.Connecting.class);
+ }
+
+ @Test
+ public void testStackEvent_toConnectedState_onDisconnectedState() {
+ allowConnection(true);
+ CsipSetCoordinatorStackEvent event = new CsipSetCoordinatorStackEvent(
+ CsipSetCoordinatorStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+ event.valueInt1 = CsipSetCoordinatorStackEvent.CONNECTION_STATE_CONNECTED;
+ sendMessageAndVerifyTransition(
+ mStateMachine.obtainMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event),
+ CsipSetCoordinatorStateMachine.Connected.class);
+ }
+
+ @Test
+ public void testProcessConnectMessage_onConnectingState() {
+ initToConnectingState();
+ mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.CONNECT);
+ TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+ Assert.assertTrue(mStateMachine.doesSuperHaveDeferredMessages(
+ CsipSetCoordinatorStateMachine.CONNECT));
+ }
+
+ @Test
+ public void testProcessConnectTimeoutMessage_onConnectingState() {
+ initToConnectingState();
+ Message msg = mStateMachine.obtainMessage(CsipSetCoordinatorStateMachine.CONNECT_TIMEOUT);
+ sendMessageAndVerifyTransition(msg, CsipSetCoordinatorStateMachine.Disconnected.class);
+ }
+
+ @Test
+ public void testProcessDisconnectMessage_onConnectingState() {
+ initToConnectingState();
+ Message msg = mStateMachine.obtainMessage(CsipSetCoordinatorStateMachine.DISCONNECT);
+ sendMessageAndVerifyTransition(msg, CsipSetCoordinatorStateMachine.Disconnected.class);
+ }
+
+ @Test
+ public void testStackEvent_withoutStateChange_onConnectingState() {
+ initToConnectingState();
+ CsipSetCoordinatorStackEvent event = new CsipSetCoordinatorStackEvent(-1);
+ mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event);
+ TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+ Assert.assertEquals(STATE_CONNECTING, mStateMachine.getConnectionState());
+
+ event = new CsipSetCoordinatorStackEvent(
+ CsipSetCoordinatorStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+ event.valueInt1 = CsipSetCoordinatorStackEvent.CONNECTION_STATE_CONNECTING;
+ mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event);
+ TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+ Assert.assertEquals(STATE_CONNECTING, mStateMachine.getConnectionState());
+
+ event = new CsipSetCoordinatorStackEvent(
+ CsipSetCoordinatorStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+ event.valueInt1 = 10000;
+ mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event);
+ TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+ Assert.assertEquals(STATE_CONNECTING, mStateMachine.getConnectionState());
+ }
+
+ @Test
+ public void testStackEvent_toDisconnectedState_onConnectingState() {
+ initToConnectingState();
+ CsipSetCoordinatorStackEvent event = new CsipSetCoordinatorStackEvent(
+ CsipSetCoordinatorStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+ event.valueInt1 = CsipSetCoordinatorStackEvent.CONNECTION_STATE_DISCONNECTED;
+ sendMessageAndVerifyTransition(
+ mStateMachine.obtainMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event),
+ CsipSetCoordinatorStateMachine.Disconnected.class);
+ }
+
+ @Test
+ public void testStackEvent_toConnectedState_onConnectingState() {
+ initToConnectingState();
+ CsipSetCoordinatorStackEvent event = new CsipSetCoordinatorStackEvent(
+ CsipSetCoordinatorStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+ event.valueInt1 = CsipSetCoordinatorStackEvent.CONNECTION_STATE_CONNECTED;
+ sendMessageAndVerifyTransition(
+ mStateMachine.obtainMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event),
+ CsipSetCoordinatorStateMachine.Connected.class);
+ }
+
+ @Test
+ public void testStackEvent_toDisconnectingState_onConnectingState() {
+ initToConnectingState();
+ CsipSetCoordinatorStackEvent event = new CsipSetCoordinatorStackEvent(
+ CsipSetCoordinatorStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+ event.valueInt1 = CsipSetCoordinatorStackEvent.CONNECTION_STATE_DISCONNECTING;
+ sendMessageAndVerifyTransition(
+ mStateMachine.obtainMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event),
+ CsipSetCoordinatorStateMachine.Disconnecting.class);
+ }
+
+ @Test
+ public void testProcessConnectMessage_onConnectedState() {
+ initToConnectedState();
+ mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.CONNECT);
+ TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+ Assert.assertEquals(STATE_CONNECTED, mStateMachine.getConnectionState());
+ }
+
+ @Test
+ public void testProcessDisconnectMessage_onConnectedState() {
+ initToConnectedState();
+ doReturn(true).when(mNativeInterface).disconnect(any(BluetoothDevice.class));
+ sendMessageAndVerifyTransition(
+ mStateMachine.obtainMessage(CsipSetCoordinatorStateMachine.DISCONNECT),
+ CsipSetCoordinatorStateMachine.Disconnecting.class);
+ }
+
+ @Test
+ public void testProcessDisconnectMessage_onConnectedState_withNativeError() {
+ initToConnectedState();
+ doReturn(false).when(mNativeInterface).disconnect(any(BluetoothDevice.class));
+ sendMessageAndVerifyTransition(
+ mStateMachine.obtainMessage(CsipSetCoordinatorStateMachine.DISCONNECT),
+ CsipSetCoordinatorStateMachine.Disconnected.class);
+ }
+
+ @Test
+ public void testStackEvent_withoutStateChange_onConnectedState() {
+ initToConnectedState();
+ CsipSetCoordinatorStackEvent event = new CsipSetCoordinatorStackEvent(-1);
+ mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event);
+ TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+ Assert.assertEquals(STATE_CONNECTED, mStateMachine.getConnectionState());
+
+ event = new CsipSetCoordinatorStackEvent(
+ CsipSetCoordinatorStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+ event.valueInt1 = CsipSetCoordinatorStackEvent.CONNECTION_STATE_CONNECTING;
+ mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event);
+ TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+ Assert.assertEquals(STATE_CONNECTED, mStateMachine.getConnectionState());
+ }
+
+ @Test
+ public void testStackEvent_toDisconnectedState_onConnectedState() {
+ initToConnectedState();
+ CsipSetCoordinatorStackEvent event = new CsipSetCoordinatorStackEvent(
+ CsipSetCoordinatorStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+ event.valueInt1 = CsipSetCoordinatorStackEvent.CONNECTION_STATE_DISCONNECTED;
+ sendMessageAndVerifyTransition(
+ mStateMachine.obtainMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event),
+ CsipSetCoordinatorStateMachine.Disconnected.class);
+ }
+
+ @Test
+ public void testStackEvent_toDisconnectingState_onConnectedState() {
+ initToConnectedState();
+ CsipSetCoordinatorStackEvent event = new CsipSetCoordinatorStackEvent(
+ CsipSetCoordinatorStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+ event.valueInt1 = CsipSetCoordinatorStackEvent.CONNECTION_STATE_DISCONNECTING;
+ sendMessageAndVerifyTransition(
+ mStateMachine.obtainMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event),
+ CsipSetCoordinatorStateMachine.Disconnecting.class);
+ }
+
+ @Test
+ public void testProcessConnectMessage_onDisconnectingState() {
+ initToDisconnectingState();
+ Message msg = mStateMachine.obtainMessage(CsipSetCoordinatorStateMachine.CONNECT);
+ mStateMachine.sendMessage(msg);
+ TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+ verify(mStateMachine).deferMessage(msg);
+ }
+
+ @Test
+ public void testProcessConnectTimeoutMessage_onDisconnectingState() {
+ initToConnectingState();
+ Message msg = mStateMachine.obtainMessage(CsipSetCoordinatorStateMachine.CONNECT_TIMEOUT);
+ sendMessageAndVerifyTransition(msg, CsipSetCoordinatorStateMachine.Disconnected.class);
+ }
+
+ @Test
+ public void testProcessDisconnectMessage_onDisconnectingState() {
+ initToDisconnectingState();
+ Message msg = mStateMachine.obtainMessage(CsipSetCoordinatorStateMachine.DISCONNECT);
+ mStateMachine.sendMessage(msg);
+ TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+ verify(mStateMachine).deferMessage(msg);
+ }
+
+ @Test
+ public void testStackEvent_withoutStateChange_onDisconnectingState() {
+ initToDisconnectingState();
+ CsipSetCoordinatorStackEvent event = new CsipSetCoordinatorStackEvent(-1);
+ mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event);
+ TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+ Assert.assertEquals(STATE_DISCONNECTING, mStateMachine.getConnectionState());
+
+ allowConnection(false);
+ event = new CsipSetCoordinatorStackEvent(
+ CsipSetCoordinatorStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+ event.valueInt1 = CsipSetCoordinatorStackEvent.CONNECTION_STATE_CONNECTED;
+ mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event);
+ TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+ verify(mNativeInterface).disconnect(any());
+
+ Mockito.clearInvocations(mNativeInterface);
+ event = new CsipSetCoordinatorStackEvent(
+ CsipSetCoordinatorStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+ event.valueInt1 = CsipSetCoordinatorStackEvent.CONNECTION_STATE_CONNECTING;
+ mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event);
+ TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+ verify(mNativeInterface).disconnect(any());
+
+ event = new CsipSetCoordinatorStackEvent(
+ CsipSetCoordinatorStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+ event.valueInt1 = CsipSetCoordinatorStackEvent.CONNECTION_STATE_DISCONNECTING;
+ mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event);
+ TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+ Assert.assertEquals(STATE_DISCONNECTING, mStateMachine.getConnectionState());
+
+ event = new CsipSetCoordinatorStackEvent(
+ CsipSetCoordinatorStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+ event.valueInt1 = 10000;
+ mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event);
+ TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+ Assert.assertEquals(STATE_DISCONNECTING, mStateMachine.getConnectionState());
+ }
+
+ @Test
+ public void testStackEvent_toConnectedState_onDisconnectingState() {
+ initToDisconnectingState();
+ allowConnection(true);
+ CsipSetCoordinatorStackEvent event = new CsipSetCoordinatorStackEvent(
+ CsipSetCoordinatorStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+ event.valueInt1 = CsipSetCoordinatorStackEvent.CONNECTION_STATE_CONNECTED;
+ sendMessageAndVerifyTransition(
+ mStateMachine.obtainMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event),
+ CsipSetCoordinatorStateMachine.Connected.class);
+ }
+
+ @Test
+ public void testStackEvent_toConnectedState_butNotAllowed_onDisconnectingState() {
+ initToDisconnectingState();
+ allowConnection(false);
+ CsipSetCoordinatorStackEvent event = new CsipSetCoordinatorStackEvent(
+ CsipSetCoordinatorStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+ event.valueInt1 = CsipSetCoordinatorStackEvent.CONNECTION_STATE_CONNECTED;
+ mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event);
+ TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+ verify(mNativeInterface).disconnect(any());
+ }
+
+ @Test
+ public void testStackEvent_toConnectingState_onDisconnectingState() {
+ initToDisconnectingState();
+ allowConnection(true);
+ CsipSetCoordinatorStackEvent event = new CsipSetCoordinatorStackEvent(
+ CsipSetCoordinatorStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+ event.valueInt1 = CsipSetCoordinatorStackEvent.CONNECTION_STATE_CONNECTING;
+ sendMessageAndVerifyTransition(
+ mStateMachine.obtainMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event),
+ CsipSetCoordinatorStateMachine.Connecting.class);
+ }
+
+ @Test
+ public void testStackEvent_toConnectingState_butNotAllowed_onDisconnectingState() {
+ initToDisconnectingState();
+ allowConnection(false);
+ CsipSetCoordinatorStackEvent event = new CsipSetCoordinatorStackEvent(
+ CsipSetCoordinatorStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+ event.valueInt1 = CsipSetCoordinatorStackEvent.CONNECTION_STATE_CONNECTED;
+ mStateMachine.sendMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event);
+ TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+ verify(mNativeInterface).disconnect(any());
+ }
+
+ private void initToConnectingState() {
+ allowConnection(true);
+ CsipSetCoordinatorStackEvent event = new CsipSetCoordinatorStackEvent(
+ CsipSetCoordinatorStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+ event.valueInt1 = CsipSetCoordinatorStackEvent.CONNECTION_STATE_CONNECTING;
+ sendMessageAndVerifyTransition(
+ mStateMachine.obtainMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event),
+ CsipSetCoordinatorStateMachine.Connecting.class);
+ allowConnection(false);
+ }
+
+ private void initToConnectedState() {
+ allowConnection(true);
+ CsipSetCoordinatorStackEvent event = new CsipSetCoordinatorStackEvent(
+ CsipSetCoordinatorStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+ event.valueInt1 = CsipSetCoordinatorStackEvent.CONNECTION_STATE_CONNECTED;
+ sendMessageAndVerifyTransition(
+ mStateMachine.obtainMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event),
+ CsipSetCoordinatorStateMachine.Connected.class);
+ allowConnection(false);
+ }
+
+ private void initToDisconnectingState() {
+ initToConnectingState();
+ CsipSetCoordinatorStackEvent event = new CsipSetCoordinatorStackEvent(
+ CsipSetCoordinatorStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+ event.valueInt1 = CsipSetCoordinatorStackEvent.CONNECTION_STATE_DISCONNECTING;
+ sendMessageAndVerifyTransition(
+ mStateMachine.obtainMessage(CsipSetCoordinatorStateMachine.STACK_EVENT, event),
+ CsipSetCoordinatorStateMachine.Disconnecting.class);
+ }
+
+ private <T> void sendMessageAndVerifyTransition(Message msg, Class<T> type) {
+ Mockito.clearInvocations(mService);
+ mStateMachine.sendMessage(msg);
+ // Verify that one connection state broadcast is executed
+ verify(mService, timeout(TIMEOUT_MS)).sendBroadcast(any(Intent.class), anyString());
+ Assert.assertThat(mStateMachine.getCurrentState(), IsInstanceOf.instanceOf(type));
+ }
+
+ public static class CsipSetCoordinatorStateMachineWrapper
+ extends CsipSetCoordinatorStateMachine {
+
+ CsipSetCoordinatorStateMachineWrapper(BluetoothDevice device,
+ CsipSetCoordinatorService svc,
+ CsipSetCoordinatorNativeInterface nativeInterface, Looper looper) {
+ super(device, svc, nativeInterface, looper);
+ }
+
+ public boolean doesSuperHaveDeferredMessages(int what) {
+ return super.hasDeferredMessages(what);
+ }
+ }
}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/gatt/GattServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/gatt/GattServiceTest.java
index 271e892..8cdc684 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/gatt/GattServiceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/gatt/GattServiceTest.java
@@ -19,11 +19,32 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.verify;
import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattService;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothStatusCodes;
+import android.bluetooth.IBluetoothGattCallback;
+import android.bluetooth.IBluetoothGattServerCallback;
+import android.bluetooth.le.AdvertiseData;
+import android.bluetooth.le.AdvertisingSetParameters;
+import android.bluetooth.le.IAdvertisingSetCallback;
+import android.bluetooth.le.IPeriodicAdvertisingCallback;
+import android.bluetooth.le.IScannerCallback;
+import android.bluetooth.le.PeriodicAdvertisingParameters;
+import android.bluetooth.le.ScanFilter;
+import android.bluetooth.le.ScanResult;
+import android.bluetooth.le.ScanSettings;
import android.content.AttributionSource;
import android.content.Context;
+import android.content.res.Resources;
+import android.os.Binder;
+import android.os.ParcelUuid;
+import android.os.RemoteException;
+import android.os.WorkSource;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -33,17 +54,27 @@
import com.android.bluetooth.R;
import com.android.bluetooth.TestUtils;
import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.btservice.CompanionManager;
import org.junit.After;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+
/**
* Test cases for {@link GattService}.
*/
@@ -58,13 +89,21 @@
private GattService mService;
@Mock private GattService.ClientMap mClientMap;
@Mock private GattService.ScannerMap mScannerMap;
+ @Mock private GattService.ScannerMap.App mApp;
+ @Mock private GattService.PendingIntentInfo mPiInfo;
+ @Mock private ScanManager mScanManager;
+ @Mock private Set<String> mReliableQueue;
+ @Mock private GattService.ServerMap mServerMap;
@Rule public final ServiceTestRule mServiceRule = new ServiceTestRule();
+ private BluetoothDevice mDevice;
private BluetoothAdapter mAdapter;
private AttributionSource mAttributionSource;
+ @Mock private Resources mResources;
@Mock private AdapterService mAdapterService;
+ private CompanionManager mBtCompanionManager;
@Before
public void setUp() throws Exception {
@@ -76,6 +115,16 @@
mAdapter = BluetoothAdapter.getDefaultAdapter();
mAttributionSource = mAdapter.getAttributionSource();
+ mDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(REMOTE_DEVICE_ADDRESS);
+
+ when(mAdapterService.getResources()).thenReturn(mResources);
+ when(mResources.getInteger(anyInt())).thenReturn(0);
+ when(mAdapterService.getSharedPreferences(anyString(), anyInt()))
+ .thenReturn(InstrumentationRegistry.getTargetContext()
+ .getSharedPreferences("GattServiceTestPrefs", Context.MODE_PRIVATE));
+
+ mBtCompanionManager = new CompanionManager(mAdapterService, null);
+ doReturn(mBtCompanionManager).when(mAdapterService).getCompanionManager();
TestUtils.startService(mServiceRule, GattService.class);
mService = GattService.getGattService();
@@ -83,6 +132,9 @@
mService.mClientMap = mClientMap;
mService.mScannerMap = mScannerMap;
+ mService.mScanManager = mScanManager;
+ mService.mReliableQueue = mReliableQueue;
+ mService.mServerMap = mServerMap;
}
@After
@@ -183,4 +235,467 @@
public void testDumpDoesNotCrash() {
mService.dump(new StringBuilder());
}
+
+ @Test
+ public void continuePiStartScan() {
+ int scannerId = 1;
+
+ mPiInfo.settings = new ScanSettings.Builder().build();
+ mApp.info = mPiInfo;
+
+ AppScanStats appScanStats = mock(AppScanStats.class);
+ doReturn(appScanStats).when(mScannerMap).getAppScanStatsById(scannerId);
+
+ mService.continuePiStartScan(scannerId, mApp);
+
+ verify(appScanStats).recordScanStart(
+ mPiInfo.settings, mPiInfo.filters, false, false, scannerId);
+ verify(mScanManager).startScan(any());
+ }
+
+ @Test
+ public void onBatchScanReportsInternal_deliverBatchScan() throws RemoteException {
+ int status = 1;
+ int scannerId = 2;
+ int reportType = ScanManager.SCAN_RESULT_TYPE_FULL;
+ int numRecords = 1;
+ byte[] recordData = new byte[]{0x01, 0x02, 0x03, 0x04, 0x05,
+ 0x06, 0x07, 0x08, 0x09, 0x00, 0x00, 0x00, 0x00};
+
+ Set<ScanClient> scanClientSet = new HashSet<>();
+ ScanClient scanClient = new ScanClient(scannerId);
+ scanClient.associatedDevices = new ArrayList<>();
+ scanClient.associatedDevices.add("02:00:00:00:00:00");
+ scanClient.scannerId = scannerId;
+ scanClientSet.add(scanClient);
+ doReturn(scanClientSet).when(mScanManager).getFullBatchScanQueue();
+ doReturn(mApp).when(mScannerMap).getById(scanClient.scannerId);
+
+ mService.onBatchScanReportsInternal(status, scannerId, reportType, numRecords, recordData);
+ verify(mScanManager).callbackDone(scannerId, status);
+
+ reportType = ScanManager.SCAN_RESULT_TYPE_TRUNCATED;
+ recordData = new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
+ 0x06, 0x04, 0x02, 0x02, 0x00, 0x00, 0x02};
+ doReturn(scanClientSet).when(mScanManager).getBatchScanQueue();
+ IScannerCallback callback = mock(IScannerCallback.class);
+ mApp.callback = callback;
+
+ mService.onBatchScanReportsInternal(status, scannerId, reportType, numRecords, recordData);
+ verify(callback).onBatchScanResults(any());
+ }
+
+ @Test
+ public void disconnectAll() {
+ Map<Integer, String> connMap = new HashMap<>();
+ int clientIf = 1;
+ String address = "02:00:00:00:00:00";
+ connMap.put(clientIf, address);
+ doReturn(connMap).when(mClientMap).getConnectedMap();
+ Integer connId = 1;
+ doReturn(connId).when(mClientMap).connIdByAddress(clientIf, address);
+
+ mService.disconnectAll(mAttributionSource);
+ }
+
+ @Test
+ public void enforceReportDelayFloor() {
+ long reportDelayFloorHigher = GattService.DEFAULT_REPORT_DELAY_FLOOR + 1;
+ ScanSettings scanSettings = new ScanSettings.Builder()
+ .setReportDelay(reportDelayFloorHigher)
+ .build();
+
+ ScanSettings newScanSettings = mService.enforceReportDelayFloor(scanSettings);
+
+ assertThat(newScanSettings.getReportDelayMillis())
+ .isEqualTo(scanSettings.getReportDelayMillis());
+
+ ScanSettings scanSettingsFloor = new ScanSettings.Builder()
+ .setReportDelay(1)
+ .build();
+
+ ScanSettings newScanSettingsFloor = mService.enforceReportDelayFloor(scanSettingsFloor);
+
+ assertThat(newScanSettingsFloor.getReportDelayMillis())
+ .isEqualTo(GattService.DEFAULT_REPORT_DELAY_FLOOR);
+ }
+
+ @Test
+ public void setAdvertisingData() {
+ int advertiserId = 1;
+ AdvertiseData data = new AdvertiseData.Builder().build();
+
+ mService.setAdvertisingData(advertiserId, data, mAttributionSource);
+ }
+
+ @Test
+ public void setAdvertisingParameters() {
+ int advertiserId = 1;
+ AdvertisingSetParameters parameters = new AdvertisingSetParameters.Builder().build();
+
+ mService.setAdvertisingParameters(advertiserId, parameters, mAttributionSource);
+ }
+
+ @Test
+ public void setPeriodicAdvertisingData() {
+ int advertiserId = 1;
+ AdvertiseData data = new AdvertiseData.Builder().build();
+
+ mService.setPeriodicAdvertisingData(advertiserId, data, mAttributionSource);
+ }
+
+ @Test
+ public void setPeriodicAdvertisingEnable() {
+ int advertiserId = 1;
+ boolean enable = true;
+
+ mService.setPeriodicAdvertisingEnable(advertiserId, enable, mAttributionSource);
+ }
+
+ @Test
+ public void setPeriodicAdvertisingParameters() {
+ int advertiserId = 1;
+ PeriodicAdvertisingParameters parameters =
+ new PeriodicAdvertisingParameters.Builder().build();
+
+ mService.setPeriodicAdvertisingParameters(advertiserId, parameters, mAttributionSource);
+ }
+
+ @Test
+ public void setScanResponseData() {
+ int advertiserId = 1;
+ AdvertiseData data = new AdvertiseData.Builder().build();
+
+ mService.setScanResponseData(advertiserId, data, mAttributionSource);
+ }
+
+ @Test
+ public void getDevicesMatchingConnectionStates() {
+ int[] states = new int[] {BluetoothProfile.STATE_CONNECTED};
+
+ BluetoothDevice testDevice = mAdapter.getRemoteDevice("00:01:02:03:04:05");
+ BluetoothDevice[] bluetoothDevices = new BluetoothDevice[]{testDevice};
+ doReturn(bluetoothDevices).when(mAdapterService).getBondedDevices();
+
+ Set<String> connectedDevices = new HashSet<>();
+ String address = "02:00:00:00:00:00";
+ connectedDevices.add(address);
+ doReturn(connectedDevices).when(mClientMap).getConnectedDevices();
+
+ List<BluetoothDevice> deviceList =
+ mService.getDevicesMatchingConnectionStates(states, mAttributionSource);
+
+ int expectedSize = 1;
+ assertThat(deviceList.size()).isEqualTo(expectedSize);
+
+ BluetoothDevice bluetoothDevice = deviceList.get(0);
+ assertThat(bluetoothDevice.getAddress()).isEqualTo(address);
+ }
+
+ @Test
+ public void registerClient() {
+ UUID uuid = UUID.randomUUID();
+ IBluetoothGattCallback callback = mock(IBluetoothGattCallback.class);
+ boolean eattSupport = true;
+
+ mService.registerClient(uuid, callback, eattSupport, mAttributionSource);
+ }
+
+ @Test
+ public void unregisterClient() {
+ int clientIf = 3;
+
+ mService.unregisterClient(clientIf, mAttributionSource);
+ verify(mClientMap).remove(clientIf);
+ }
+
+ @Test
+ public void registerScanner() throws Exception {
+ IScannerCallback callback = mock(IScannerCallback.class);
+ WorkSource workSource = mock(WorkSource.class);
+
+ AppScanStats appScanStats = mock(AppScanStats.class);
+ doReturn(appScanStats).when(mScannerMap).getAppScanStatsByUid(Binder.getCallingUid());
+
+ mService.registerScanner(callback, workSource, mAttributionSource);
+ verify(mScannerMap).add(any(), eq(workSource), eq(callback), eq(null), eq(mService));
+ verify(mScanManager).registerScanner(any());
+ }
+
+ @Test
+ public void flushPendingBatchResults() {
+ int scannerId = 3;
+
+ mService.flushPendingBatchResults(scannerId, mAttributionSource);
+ verify(mScanManager).flushBatchScanResults(new ScanClient(scannerId));
+ }
+
+ @Test
+ public void readCharacteristic() {
+ int clientIf = 1;
+ String address = REMOTE_DEVICE_ADDRESS;
+ int handle = 2;
+ int authReq = 3;
+
+ Integer connId = 1;
+ doReturn(connId).when(mClientMap).connIdByAddress(clientIf, address);
+
+ mService.readCharacteristic(clientIf, address, handle, authReq, mAttributionSource);
+ }
+
+ @Test
+ public void readUsingCharacteristicUuid() {
+ int clientIf = 1;
+ String address = REMOTE_DEVICE_ADDRESS;
+ UUID uuid = UUID.randomUUID();
+ int startHandle = 2;
+ int endHandle = 3;
+ int authReq = 4;
+
+ Integer connId = 1;
+ doReturn(connId).when(mClientMap).connIdByAddress(clientIf, address);
+
+ mService.readUsingCharacteristicUuid(clientIf, address, uuid, startHandle, endHandle,
+ authReq, mAttributionSource);
+ }
+
+ @Test
+ public void writeCharacteristic() {
+ int clientIf = 1;
+ String address = REMOTE_DEVICE_ADDRESS;
+ int handle = 2;
+ int writeType = 3;
+ int authReq = 4;
+ byte[] value = new byte[] {5, 6};
+
+ Integer connId = 1;
+ doReturn(connId).when(mClientMap).connIdByAddress(clientIf, address);
+
+ int writeCharacteristicResult = mService.writeCharacteristic(clientIf, address, handle,
+ writeType, authReq, value, mAttributionSource);
+ assertThat(writeCharacteristicResult)
+ .isEqualTo(BluetoothStatusCodes.ERROR_DEVICE_NOT_CONNECTED);
+ }
+
+ @Test
+ public void readDescriptor() throws Exception {
+ int clientIf = 1;
+ String address = REMOTE_DEVICE_ADDRESS;
+ int handle = 2;
+ int authReq = 3;
+
+ Integer connId = 1;
+ doReturn(connId).when(mClientMap).connIdByAddress(clientIf, address);
+
+ mService.readDescriptor(clientIf, address, handle, authReq, mAttributionSource);
+ }
+
+ @Test
+ public void beginReliableWrite() {
+ int clientIf = 1;
+ String address = REMOTE_DEVICE_ADDRESS;
+
+ mService.beginReliableWrite(clientIf, address, mAttributionSource);
+ verify(mReliableQueue).add(address);
+ }
+
+ @Test
+ public void endReliableWrite() {
+ int clientIf = 1;
+ String address = REMOTE_DEVICE_ADDRESS;
+ boolean execute = true;
+
+ Integer connId = 1;
+ doReturn(connId).when(mClientMap).connIdByAddress(clientIf, address);
+
+ mService.endReliableWrite(clientIf, address, execute, mAttributionSource);
+ verify(mReliableQueue).remove(address);
+ }
+
+ @Test
+ public void registerForNotification() throws Exception {
+ int clientIf = 1;
+ String address = REMOTE_DEVICE_ADDRESS;
+ int handle = 2;
+ boolean enable = true;
+
+ Integer connId = 1;
+ doReturn(connId).when(mClientMap).connIdByAddress(clientIf, address);
+
+ mService.registerForNotification(clientIf, address, handle, enable, mAttributionSource);
+ }
+
+ @Test
+ public void readRemoteRssi() {
+ int clientIf = 1;
+ String address = REMOTE_DEVICE_ADDRESS;
+
+ mService.readRemoteRssi(clientIf, address, mAttributionSource);
+ }
+
+ @Test
+ public void configureMTU() {
+ int clientIf = 1;
+ String address = REMOTE_DEVICE_ADDRESS;
+ int mtu = 2;
+
+ Integer connId = 1;
+ doReturn(connId).when(mClientMap).connIdByAddress(clientIf, address);
+
+ mService.configureMTU(clientIf, address, mtu, mAttributionSource);
+ }
+
+ @Test
+ public void leConnectionUpdate() throws Exception {
+ int clientIf = 1;
+ String address = REMOTE_DEVICE_ADDRESS;
+ int minInterval = 3;
+ int maxInterval = 4;
+ int peripheralLatency = 5;
+ int supervisionTimeout = 6;
+ int minConnectionEventLen = 7;
+ int maxConnectionEventLen = 8;
+
+ mService.leConnectionUpdate(clientIf, address, minInterval, maxInterval,
+ peripheralLatency, supervisionTimeout, minConnectionEventLen,
+ maxConnectionEventLen, mAttributionSource);
+ }
+
+ @Test
+ public void serverConnect() {
+ int serverIf = 1;
+ String address = REMOTE_DEVICE_ADDRESS;
+ boolean isDirect = true;
+ int transport = 2;
+
+ mService.serverConnect(serverIf, address, isDirect, transport, mAttributionSource);
+ }
+
+ @Test
+ public void serverDisconnect() {
+ int serverIf = 1;
+ String address = REMOTE_DEVICE_ADDRESS;
+
+ Integer connId = 1;
+ doReturn(connId).when(mServerMap).connIdByAddress(serverIf, address);
+
+ mService.serverDisconnect(serverIf, address, mAttributionSource);
+ }
+
+ @Test
+ public void serverSetPreferredPhy() throws Exception {
+ int serverIf = 1;
+ String address = REMOTE_DEVICE_ADDRESS;
+ int txPhy = 2;
+ int rxPhy = 1;
+ int phyOptions = 3;
+
+ mService.serverSetPreferredPhy(serverIf, address, txPhy, rxPhy, phyOptions,
+ mAttributionSource);
+ }
+
+ @Test
+ public void serverReadPhy() {
+ int serverIf = 1;
+ String address = REMOTE_DEVICE_ADDRESS;
+
+ mService.serverReadPhy(serverIf, address, mAttributionSource);
+ }
+
+ @Test
+ public void sendNotification() throws Exception {
+ int serverIf = 1;
+ String address = REMOTE_DEVICE_ADDRESS;
+ int handle = 2;
+ boolean confirm = true;
+ byte[] value = new byte[] {5, 6};;
+
+ Integer connId = 1;
+ doReturn(connId).when(mServerMap).connIdByAddress(serverIf, address);
+
+ mService.sendNotification(serverIf, address, handle, confirm, value, mAttributionSource);
+
+ confirm = false;
+
+ mService.sendNotification(serverIf, address, handle, confirm, value, mAttributionSource);
+ }
+
+ @Test
+ public void getOwnAddress() throws Exception {
+ int advertiserId = 1;
+
+ mService.getOwnAddress(advertiserId, mAttributionSource);
+ }
+
+ @Test
+ public void enableAdvertisingSet() throws Exception {
+ int advertiserId = 1;
+ boolean enable = true;
+ int duration = 3;
+ int maxExtAdvEvents = 4;
+
+ mService.enableAdvertisingSet(advertiserId, enable, duration, maxExtAdvEvents,
+ mAttributionSource);
+ }
+
+ @Ignore("b/265327402")
+ @Test
+ public void registerSync() {
+ ScanResult scanResult = new ScanResult(mDevice, 1, 2, 3, 4, 5, 6, 7, null, 8);
+ int skip = 1;
+ int timeout = 2;
+ IPeriodicAdvertisingCallback callback = mock(IPeriodicAdvertisingCallback.class);
+
+ mService.registerSync(scanResult, skip, timeout, callback, mAttributionSource);
+ }
+
+ @Test
+ public void transferSync() {
+ int serviceData = 1;
+ int syncHandle = 2;
+
+ mService.transferSync(mDevice, serviceData, syncHandle, mAttributionSource);
+ }
+
+ @Ignore("b/265327402")
+ @Test
+ public void transferSetInfo() {
+ int serviceData = 1;
+ int advHandle = 2;
+ IPeriodicAdvertisingCallback callback = mock(IPeriodicAdvertisingCallback.class);
+
+ mService.transferSetInfo(mDevice, serviceData, advHandle, callback,
+ mAttributionSource);
+ }
+
+ @Ignore("b/265327402")
+ @Test
+ public void unregisterSync() {
+ IPeriodicAdvertisingCallback callback = mock(IPeriodicAdvertisingCallback.class);
+
+ mService.unregisterSync(callback, mAttributionSource);
+ }
+
+ @Test
+ public void unregAll() throws Exception {
+ int appId = 1;
+ List<Integer> appIds = new ArrayList<>();
+ appIds.add(appId);
+ doReturn(appIds).when(mClientMap).getAllAppsIds();
+
+ mService.unregAll(mAttributionSource);
+ verify(mClientMap).remove(appId);
+ }
+
+ @Test
+ public void numHwTrackFiltersAvailable() {
+ mService.numHwTrackFiltersAvailable(mAttributionSource);
+ verify(mScanManager).getCurrentUsedTrackingAdvertisement();
+ }
+
+ @Test
+ public void cleanUp_doesNotCrash() {
+ mService.cleanup();
+ }
}
+
diff --git a/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceAndStateMachineTest.java b/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceAndStateMachineTest.java
index c89e135..c41cbce 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceAndStateMachineTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceAndStateMachineTest.java
@@ -26,6 +26,7 @@
import android.app.Activity;
import android.app.Instrumentation;
import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothSinkAudioPolicy;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothProfile;
@@ -66,6 +67,7 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
+import org.mockito.InOrder;
import java.lang.reflect.Method;
import java.util.Collections;
@@ -181,6 +183,8 @@
.getBondState(any(BluetoothDevice.class));
doAnswer(invocation -> mBondedDevices.toArray(new BluetoothDevice[]{})).when(
mAdapterService).getBondedDevices();
+ doReturn(new BluetoothSinkAudioPolicy.Builder().build()).when(mAdapterService)
+ .getRequestedAudioPolicyAsSink(any(BluetoothDevice.class));
// Mock system interface
doNothing().when(mSystemInterface).stop();
when(mSystemInterface.getHeadsetPhoneState()).thenReturn(mPhoneState);
@@ -663,6 +667,7 @@
Assert.assertTrue(mHeadsetService.setActiveDevice(device));
verify(mNativeInterface).setActiveDevice(device);
Assert.assertEquals(device, mHeadsetService.getActiveDevice());
+ verify(mNativeInterface).sendBsir(eq(device), eq(true));
// Start voice recognition
startVoiceRecognitionFromHf(device);
}
@@ -686,6 +691,7 @@
Assert.assertTrue(mHeadsetService.setActiveDevice(device));
verify(mNativeInterface).setActiveDevice(device);
Assert.assertEquals(device, mHeadsetService.getActiveDevice());
+ verify(mNativeInterface).sendBsir(eq(device), eq(true));
// Start voice recognition
startVoiceRecognitionFromHf(device);
// Stop voice recognition
@@ -720,6 +726,7 @@
Assert.assertTrue(mHeadsetService.setActiveDevice(device));
verify(mNativeInterface).setActiveDevice(device);
Assert.assertEquals(device, mHeadsetService.getActiveDevice());
+ verify(mNativeInterface).sendBsir(eq(device), eq(true));
// Start voice recognition
HeadsetStackEvent startVrEvent =
new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_VR_STATE_CHANGED,
@@ -752,6 +759,7 @@
Assert.assertTrue(mHeadsetService.setActiveDevice(device));
verify(mNativeInterface).setActiveDevice(device);
Assert.assertEquals(device, mHeadsetService.getActiveDevice());
+ verify(mNativeInterface).sendBsir(eq(device), eq(true));
// Start voice recognition
HeadsetStackEvent startVrEvent =
new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_VR_STATE_CHANGED,
@@ -784,6 +792,7 @@
Assert.assertTrue(mHeadsetService.setActiveDevice(device));
verify(mNativeInterface).setActiveDevice(device);
Assert.assertEquals(device, mHeadsetService.getActiveDevice());
+ verify(mNativeInterface).sendBsir(eq(device), eq(true));
// Start voice recognition
startVoiceRecognitionFromAg();
}
@@ -857,6 +866,7 @@
Assert.assertTrue(mHeadsetService.setActiveDevice(device));
verify(mNativeInterface).setActiveDevice(device);
Assert.assertEquals(device, mHeadsetService.getActiveDevice());
+ verify(mNativeInterface).sendBsir(eq(device), eq(true));
// Start voice recognition
startVoiceRecognitionFromAg();
// Stop voice recognition
@@ -905,8 +915,10 @@
connectTestDevice(deviceA);
BluetoothDevice deviceB = TestUtils.getTestDevice(mAdapter, 1);
connectTestDevice(deviceB);
- verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).sendBsir(deviceA, false);
- verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).sendBsir(deviceB, false);
+ InOrder inOrder = inOrder(mNativeInterface);
+ inOrder.verify(mNativeInterface).sendBsir(eq(deviceA), eq(true));
+ inOrder.verify(mNativeInterface).sendBsir(eq(deviceB), eq(false));
+ inOrder.verify(mNativeInterface).sendBsir(eq(deviceA), eq(false));
// Set active device to device B
Assert.assertTrue(mHeadsetService.setActiveDevice(deviceB));
verify(mNativeInterface).setActiveDevice(deviceB);
@@ -957,8 +969,10 @@
connectTestDevice(deviceA);
BluetoothDevice deviceB = TestUtils.getTestDevice(mAdapter, 1);
connectTestDevice(deviceB);
- verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).sendBsir(deviceA, false);
- verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).sendBsir(deviceB, false);
+ InOrder inOrder = inOrder(mNativeInterface);
+ inOrder.verify(mNativeInterface).sendBsir(eq(deviceA), eq(true));
+ inOrder.verify(mNativeInterface).sendBsir(eq(deviceB), eq(false));
+ inOrder.verify(mNativeInterface).sendBsir(eq(deviceA), eq(false));
// Set active device to device B
Assert.assertTrue(mHeadsetService.setActiveDevice(deviceB));
verify(mNativeInterface).setActiveDevice(deviceB);
@@ -1009,8 +1023,10 @@
connectTestDevice(deviceA);
BluetoothDevice deviceB = TestUtils.getTestDevice(mAdapter, 1);
connectTestDevice(deviceB);
- verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).sendBsir(deviceA, false);
- verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).sendBsir(deviceB, false);
+ InOrder inOrder = inOrder(mNativeInterface);
+ inOrder.verify(mNativeInterface).sendBsir(eq(deviceA), eq(true));
+ inOrder.verify(mNativeInterface).sendBsir(eq(deviceB), eq(false));
+ inOrder.verify(mNativeInterface).sendBsir(eq(deviceA), eq(false));
// Set active device to device B
Assert.assertTrue(mHeadsetService.setActiveDevice(deviceB));
verify(mNativeInterface).setActiveDevice(deviceB);
@@ -1048,8 +1064,10 @@
connectTestDevice(deviceA);
BluetoothDevice deviceB = TestUtils.getTestDevice(mAdapter, 1);
connectTestDevice(deviceB);
- verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).sendBsir(deviceA, false);
- verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).sendBsir(deviceB, false);
+ InOrder inOrder = inOrder(mNativeInterface);
+ inOrder.verify(mNativeInterface).sendBsir(eq(deviceA), eq(true));
+ inOrder.verify(mNativeInterface).sendBsir(eq(deviceB), eq(false));
+ inOrder.verify(mNativeInterface).sendBsir(eq(deviceA), eq(false));
// Set active device to device B
Assert.assertTrue(mHeadsetService.setActiveDevice(deviceB));
verify(mNativeInterface).setActiveDevice(deviceB);
diff --git a/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceTest.java
index e071563..09fe7cc 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceTest.java
@@ -19,6 +19,7 @@
import static org.mockito.Mockito.*;
import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothSinkAudioPolicy;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothProfile;
@@ -116,6 +117,8 @@
Set<BluetoothDevice> keys = mStateMachines.keySet();
return keys.toArray(new BluetoothDevice[keys.size()]);
}).when(mAdapterService).getBondedDevices();
+ doReturn(new BluetoothSinkAudioPolicy.Builder().build()).when(mAdapterService)
+ .getRequestedAudioPolicyAsSink(any(BluetoothDevice.class));
// Mock system interface
doNothing().when(mSystemInterface).stop();
when(mSystemInterface.getHeadsetPhoneState()).thenReturn(mPhoneState);
@@ -991,6 +994,44 @@
mHeadsetService.dump(sb);
}
+ @Test
+ public void testConnectDeviceNotAllowedInbandRingPolicy_InbandRingStatus() {
+ when(mDatabaseManager.getProfileConnectionPolicy(any(BluetoothDevice.class),
+ eq(BluetoothProfile.HEADSET)))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_UNKNOWN);
+ mCurrentDevice = TestUtils.getTestDevice(mAdapter, 0);
+ Assert.assertTrue(mHeadsetService.connect(mCurrentDevice));
+ when(mStateMachines.get(mCurrentDevice).getDevice()).thenReturn(mCurrentDevice);
+ when(mStateMachines.get(mCurrentDevice).getConnectionState()).thenReturn(
+ BluetoothProfile.STATE_CONNECTED);
+ when(mStateMachines.get(mCurrentDevice).getConnectingTimestampMs()).thenReturn(
+ SystemClock.uptimeMillis());
+ Assert.assertEquals(Collections.singletonList(mCurrentDevice),
+ mHeadsetService.getConnectedDevices());
+ mHeadsetService.onConnectionStateChangedFromStateMachine(mCurrentDevice,
+ BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_CONNECTED);
+
+ when(mStateMachines.get(mCurrentDevice).getHfpCallAudioPolicy()).thenReturn(
+ new BluetoothSinkAudioPolicy.Builder()
+ .setCallEstablishPolicy(BluetoothSinkAudioPolicy.POLICY_ALLOWED)
+ .setActiveDevicePolicyAfterConnection(
+ BluetoothSinkAudioPolicy.POLICY_ALLOWED)
+ .setInBandRingtonePolicy(BluetoothSinkAudioPolicy.POLICY_ALLOWED)
+ .build()
+ );
+ Assert.assertEquals(true, mHeadsetService.isInbandRingingEnabled());
+
+ when(mStateMachines.get(mCurrentDevice).getHfpCallAudioPolicy()).thenReturn(
+ new BluetoothSinkAudioPolicy.Builder()
+ .setCallEstablishPolicy(BluetoothSinkAudioPolicy.POLICY_ALLOWED)
+ .setActiveDevicePolicyAfterConnection(
+ BluetoothSinkAudioPolicy.POLICY_ALLOWED)
+ .setInBandRingtonePolicy(BluetoothSinkAudioPolicy.POLICY_NOT_ALLOWED)
+ .build()
+ );
+ Assert.assertEquals(false, mHeadsetService.isInbandRingingEnabled());
+ }
+
private void addConnectedDeviceHelper(BluetoothDevice device) {
mCurrentDevice = device;
when(mDatabaseManager.getProfileConnectionPolicy(any(BluetoothDevice.class),
diff --git a/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetStateMachineTest.java b/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetStateMachineTest.java
index 971e53b..025fe05 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetStateMachineTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetStateMachineTest.java
@@ -51,12 +51,14 @@
import com.android.bluetooth.R;
import com.android.bluetooth.TestUtils;
import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.btservice.storage.DatabaseManager;
import org.hamcrest.core.IsInstanceOf;
import org.junit.After;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -85,6 +87,7 @@
private ArgumentCaptor<Intent> mIntentArgument = ArgumentCaptor.forClass(Intent.class);
@Mock private AdapterService mAdapterService;
+ @Mock private DatabaseManager mDatabaseManager;
@Mock private HeadsetService mHeadsetService;
@Mock private HeadsetSystemInterface mSystemInterface;
@Mock private AudioManager mAudioManager;
@@ -108,6 +111,9 @@
mAdapter = BluetoothAdapter.getDefaultAdapter();
// Get a device for testing
mTestDevice = mAdapter.getRemoteDevice("00:01:02:03:04:05");
+ // Get a database
+ doReturn(mDatabaseManager).when(mAdapterService).getDatabase();
+ doReturn(true).when(mDatabaseManager).setAudioPolicyMetadata(anyObject(), anyObject());
// Spy on native interface
mNativeInterface = spy(HeadsetNativeInterface.getInstance());
doNothing().when(mNativeInterface).init(anyInt(), anyBoolean());
@@ -1186,6 +1192,7 @@
Assert.assertEquals(mHeadsetStateMachine.parseUnknownAt(atString), "A\"command\"");
}
+ @Ignore("b/265556073")
@Test
public void testHandleAccessPermissionResult_withNoChangeInAtCommandResult() {
when(mIntent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)).thenReturn(null);
@@ -1435,6 +1442,22 @@
}
/**
+ * A test to validate received Android AT commands and processing
+ */
+ @Test
+ public void testProcessAndroidAt() {
+ setUpConnectedState();
+ // setup Audio Policy Feature
+ setUpAudioPolicy();
+ // receive and set android policy
+ mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
+ new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_UNKNOWN_AT,
+ "+ANDROID=1,1,1,1", mTestDevice));
+ verify(mDatabaseManager, timeout(ASYNC_CALL_TIMEOUT_MILLIS))
+ .setAudioPolicyMetadata(anyObject(), anyObject());
+ }
+
+ /**
* Setup Connecting State
* @return number of times mHeadsetService.sendBroadcastAsUser() has been invoked
*/
@@ -1548,4 +1571,12 @@
IsInstanceOf.instanceOf(HeadsetStateMachine.Disconnecting.class));
return numBroadcastsSent;
}
+
+ private void setUpAudioPolicy() {
+ mHeadsetStateMachine.sendMessage(HeadsetStateMachine.STACK_EVENT,
+ new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_UNKNOWN_AT,
+ "+ANDROID=?", mTestDevice));
+ verify(mNativeInterface, timeout(ASYNC_CALL_TIMEOUT_MILLIS)).atResponseString(
+ anyObject(), anyString());
+ }
}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientServiceTest.java
index e85dc17..be9df4b 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientServiceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientServiceTest.java
@@ -16,6 +16,7 @@
package com.android.bluetooth.hfpclient;
+import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.doReturn;
@@ -25,6 +26,8 @@
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothSinkAudioPolicy;
import android.bluetooth.BluetoothManager;
import android.content.Context;
import android.content.Intent;
@@ -148,4 +151,17 @@
mService.dump(new StringBuilder());
}
+
+ @Test
+ public void testSetCallAudioPolicy() {
+ // Put mock state machine
+ BluetoothDevice device =
+ BluetoothAdapter.getDefaultAdapter().getRemoteDevice("00:01:02:03:04:05");
+ mService.getStateMachineMap().put(device, mStateMachine);
+
+ mService.setAudioPolicy(device, new BluetoothSinkAudioPolicy.Builder().build());
+
+ verify(mStateMachine, timeout(STANDARD_WAIT_MILLIS).times(1))
+ .setAudioPolicy(any(BluetoothSinkAudioPolicy.class));
+ }
}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineTest.java b/android/app/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineTest.java
index 0aa98f2..dc8683d 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/hfpclient/HeadsetClientStateMachineTest.java
@@ -1,5 +1,26 @@
+/*
+ * Copyright 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package com.android.bluetooth.hfpclient;
+import static android.bluetooth.BluetoothProfile.STATE_CONNECTED;
+import static android.bluetooth.BluetoothProfile.STATE_CONNECTING;
+import static android.bluetooth.BluetoothProfile.STATE_DISCONNECTED;
+import static android.bluetooth.BluetoothProfile.STATE_DISCONNECTING;
+
import static com.android.bluetooth.hfpclient.HeadsetClientStateMachine.AT_OK;
import static com.android.bluetooth.hfpclient.HeadsetClientStateMachine.ENTER_PRIVATE_MODE;
import static com.android.bluetooth.hfpclient.HeadsetClientStateMachine.EXPLICIT_CALL_TRANSFER;
@@ -13,6 +34,7 @@
import android.bluetooth.BluetoothAssignedNumbers;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadsetClient;
+import android.bluetooth.BluetoothSinkAudioPolicy;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.Intent;
@@ -20,6 +42,7 @@
import android.media.AudioManager;
import android.os.Bundle;
import android.os.HandlerThread;
+import android.os.Looper;
import android.os.Message;
import android.util.Pair;
@@ -35,6 +58,8 @@
import com.android.bluetooth.TestUtils;
import com.android.bluetooth.Utils;
import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.hfp.HeadsetService;
+import com.android.bluetooth.hfp.HeadsetStackEvent;
import org.hamcrest.core.AllOf;
import org.hamcrest.core.IsInstanceOf;
@@ -46,18 +71,22 @@
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.hamcrest.MockitoHamcrest;
import java.util.List;
import java.util.Set;
+/**
+ * Test cases for {@link HeadsetClientStateMachine}.
+ */
@LargeTest
@RunWith(AndroidJUnit4.class)
public class HeadsetClientStateMachineTest {
private BluetoothAdapter mAdapter;
private HandlerThread mHandlerThread;
- private HeadsetClientStateMachine mHeadsetClientStateMachine;
+ private TestHeadsetClientStateMachine mHeadsetClientStateMachine;
private BluetoothDevice mTestDevice;
private Context mTargetContext;
@@ -66,6 +95,8 @@
@Mock
private Resources mMockHfpResources;
@Mock
+ private HeadsetService mHeadsetService;
+ @Mock
private HeadsetClientService mHeadsetClientService;
@Mock
private AudioManager mAudioManager;
@@ -76,6 +107,7 @@
private static final int QUERY_CURRENT_CALLS_WAIT_MILLIS = 2000;
private static final int QUERY_CURRENT_CALLS_TEST_WAIT_MILLIS = QUERY_CURRENT_CALLS_WAIT_MILLIS
* 3 / 2;
+ private static final int TIMEOUT_MS = 1000;
@Before
public void setUp() throws Exception {
@@ -97,6 +129,7 @@
TestUtils.setAdapterService(mAdapterService);
mNativeInterface = spy(NativeInterface.getInstance());
+ doReturn(true).when(mNativeInterface).sendAndroidAt(anyObject(), anyString());
// This line must be called to make sure relevant objects are initialized properly
mAdapter = BluetoothAdapter.getDefaultAdapter();
@@ -107,9 +140,8 @@
mHandlerThread = new HandlerThread("HeadsetClientStateMachineTestHandlerThread");
mHandlerThread.start();
// Manage looper execution in main test thread explicitly to guarantee timing consistency
- mHeadsetClientStateMachine =
- new HeadsetClientStateMachine(mHeadsetClientService, mHandlerThread.getLooper(),
- mNativeInterface);
+ mHeadsetClientStateMachine = new TestHeadsetClientStateMachine(mHeadsetClientService,
+ mHeadsetService, mHandlerThread.getLooper(), mNativeInterface);
mHeadsetClientStateMachine.start();
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
}
@@ -120,9 +152,12 @@
return;
}
TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
- TestUtils.clearAdapterService(mAdapterService);
+ mHeadsetClientStateMachine.allowConnect = null;
mHeadsetClientStateMachine.doQuit();
+ TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
mHandlerThread.quit();
+ TestUtils.clearAdapterService(mAdapterService);
+ verifyNoMoreInteractions(mHeadsetService);
}
/**
@@ -201,6 +236,9 @@
slcEvent.valueInt2 = HeadsetClientHalConstants.PEER_FEAT_ECS;
slcEvent.device = mTestDevice;
mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, slcEvent);
+ TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+
+ setUpAndroidAt(false);
// Verify that one connection state broadcast is executed
ArgumentCaptor<Intent> intentArgument2 = ArgumentCaptor.forClass(Intent.class);
@@ -212,6 +250,7 @@
// Check we are in connecting state now.
Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
IsInstanceOf.instanceOf(HeadsetClientStateMachine.Connected.class));
+ verify(mHeadsetService).updateInbandRinging(eq(mTestDevice), eq(true));
}
/**
@@ -254,6 +293,7 @@
// Check we are in connecting state now.
Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
IsInstanceOf.instanceOf(HeadsetClientStateMachine.Disconnected.class));
+ verify(mHeadsetService).updateInbandRinging(eq(mTestDevice), eq(false));
}
/**
@@ -293,6 +333,9 @@
slcEvent.valueInt2 = HeadsetClientHalConstants.PEER_FEAT_ECS;
slcEvent.device = mTestDevice;
mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, slcEvent);
+ TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+
+ setUpAndroidAt(false);
verify(mHeadsetClientService,
timeout(STANDARD_WAIT_MILLIS).times(expectedBroadcastMultiplePermissionsIndex++))
@@ -302,6 +345,8 @@
Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
intentArgument.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
+ verify(mHeadsetService).updateInbandRinging(eq(mTestDevice), eq(true));
+
StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_IN_BAND_RINGTONE);
event.valueInt = 0;
event.device = mTestDevice;
@@ -396,6 +441,10 @@
/* Utility function to simulate SLC connection. */
private int setUpServiceLevelConnection(int startBroadcastIndex) {
+ return setUpServiceLevelConnection(startBroadcastIndex, false);
+ }
+
+ private int setUpServiceLevelConnection(int startBroadcastIndex, boolean androidAtSupported) {
// Trigger SLC connection
StackEvent slcEvent = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
slcEvent.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_SLC_CONNECTED;
@@ -403,16 +452,47 @@
slcEvent.valueInt2 |= HeadsetClientHalConstants.PEER_FEAT_HF_IND;
slcEvent.device = mTestDevice;
mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, slcEvent);
+ TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+
+ setUpAndroidAt(androidAtSupported);
+
ArgumentCaptor<Intent> intentArgument = ArgumentCaptor.forClass(Intent.class);
verify(mHeadsetClientService, timeout(STANDARD_WAIT_MILLIS).times(startBroadcastIndex))
.sendBroadcastMultiplePermissions(intentArgument.capture(),
any(String[].class), any(BroadcastOptions.class));
Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
intentArgument.getValue().getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
+ Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
+ IsInstanceOf.instanceOf(HeadsetClientStateMachine.Connected.class));
+ verify(mHeadsetService).updateInbandRinging(eq(mTestDevice), eq(true));
+
startBroadcastIndex++;
return startBroadcastIndex;
}
+ /**
+ * Set up and verify AT Android related commands and events.
+ * Make sure this method is invoked after SLC is setup.
+ */
+ private void setUpAndroidAt(boolean androidAtSupported) {
+ verify(mNativeInterface).sendAndroidAt(mTestDevice, "+ANDROID=?");
+ if (androidAtSupported) {
+ StackEvent unknownEvt = new StackEvent(StackEvent.EVENT_TYPE_UNKNOWN_EVENT);
+ unknownEvt.valueString = "+ANDROID: 1";
+ unknownEvt.device = mTestDevice;
+ mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, unknownEvt);
+ TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+ verify(mHeadsetClientService).setAudioPolicyRemoteSupported(mTestDevice, true);
+ mHeadsetClientStateMachine.setAudioPolicyRemoteSupported(true);
+ } else {
+ // receive CMD_RESULT CME_ERROR due to remote not supporting Android AT
+ StackEvent cmdResEvt = new StackEvent(StackEvent.EVENT_TYPE_CMD_RESULT);
+ cmdResEvt.valueInt = StackEvent.CMD_RESULT_TYPE_CME_ERROR;
+ cmdResEvt.device = mTestDevice;
+ mHeadsetClientStateMachine.sendMessage(StackEvent.STACK_EVENT, cmdResEvt);
+ }
+ }
+
/* Utility function: supported AT command should lead to native call */
private void runSupportedVendorAtCommand(String atCommand, int vendorId) {
// Return true for priority.
@@ -991,4 +1071,361 @@
Assert.assertEquals(HeadsetClientStateMachine.getMessageName(unknownMessageInt),
"UNKNOWN(" + unknownMessageInt + ")");
}
+ /**
+ * Tests and verify behavior of the case where remote device doesn't support
+ * At Android but tries to send audio policy.
+ */
+ @Test
+ public void testAndroidAtRemoteNotSupported_StateTransition_setAudioPolicy() {
+ // Setup connection state machine to be in connected state
+ when(mHeadsetClientService.getConnectionPolicy(any(BluetoothDevice.class))).thenReturn(
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ int expectedBroadcastIndex = 1;
+
+ expectedBroadcastIndex = setUpHfpClientConnection(expectedBroadcastIndex);
+ expectedBroadcastIndex = setUpServiceLevelConnection(expectedBroadcastIndex);
+
+ BluetoothSinkAudioPolicy dummyAudioPolicy = new BluetoothSinkAudioPolicy.Builder().build();
+ mHeadsetClientStateMachine.setAudioPolicy(dummyAudioPolicy);
+ verify(mNativeInterface, never()).sendAndroidAt(mTestDevice, "+ANDROID:1,0,0,0");
+ }
+
+ @SmallTest
+ @Test
+ public void testSetGetCallAudioPolicy() {
+ // Return true for priority.
+ when(mHeadsetClientService.getConnectionPolicy(any(BluetoothDevice.class))).thenReturn(
+ BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+
+ int expectedBroadcastIndex = 1;
+
+ expectedBroadcastIndex = setUpHfpClientConnection(expectedBroadcastIndex);
+ expectedBroadcastIndex = setUpServiceLevelConnection(expectedBroadcastIndex, true);
+
+ BluetoothSinkAudioPolicy dummyAudioPolicy = new BluetoothSinkAudioPolicy.Builder()
+ .setCallEstablishPolicy(BluetoothSinkAudioPolicy.POLICY_ALLOWED)
+ .setActiveDevicePolicyAfterConnection(BluetoothSinkAudioPolicy.POLICY_NOT_ALLOWED)
+ .setInBandRingtonePolicy(BluetoothSinkAudioPolicy.POLICY_ALLOWED)
+ .build();
+
+ mHeadsetClientStateMachine.setAudioPolicy(dummyAudioPolicy);
+ verify(mNativeInterface).sendAndroidAt(mTestDevice, "+ANDROID=1,1,2,1");
+ }
+
+ @Test
+ public void testDumpDoesNotCrash() {
+ mHeadsetClientStateMachine.dump(new StringBuilder());
+ }
+
+ @Test
+ public void testProcessDisconnectMessage_onDisconnectedState() {
+ mHeadsetClientStateMachine.sendMessage(HeadsetClientStateMachine.DISCONNECT);
+ TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+ Assert.assertEquals(STATE_DISCONNECTED,
+ mHeadsetClientStateMachine.getConnectionState(mTestDevice));
+ }
+
+ @Test
+ public void testProcessConnectMessage_onDisconnectedState() {
+ doReturn(true).when(mNativeInterface).connect(any(BluetoothDevice.class));
+ sendMessageAndVerifyTransition(
+ mHeadsetClientStateMachine
+ .obtainMessage(HeadsetClientStateMachine.CONNECT, mTestDevice),
+ HeadsetClientStateMachine.Connecting.class);
+ }
+
+ @Test
+ public void testStackEvent_toConnectingState_onDisconnectedState() {
+ allowConnection(true);
+ StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+ event.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_CONNECTED;
+ event.device = mTestDevice;
+ sendMessageAndVerifyTransition(
+ mHeadsetClientStateMachine.obtainMessage(StackEvent.STACK_EVENT, event),
+ HeadsetClientStateMachine.Connecting.class);
+ }
+
+ @Test
+ public void testStackEvent_toConnectingState_disallowConnection_onDisconnectedState() {
+ allowConnection(false);
+ StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+ event.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_CONNECTED;
+ event.device = mTestDevice;
+ sendMessageAndVerifyTransition(
+ mHeadsetClientStateMachine.obtainMessage(StackEvent.STACK_EVENT, event),
+ HeadsetClientStateMachine.Disconnected.class);
+ }
+
+ @Test
+ public void testProcessConnectMessage_onConnectingState() {
+ initToConnectingState();
+ mHeadsetClientStateMachine.sendMessage(HeadsetClientStateMachine.CONNECT);
+ TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+ Assert.assertTrue(mHeadsetClientStateMachine.doesSuperHaveDeferredMessages(
+ HeadsetClientStateMachine.CONNECT));
+ }
+
+ @Test
+ public void testProcessStackEvent_ConnectionStateChanged_Disconnected_onConnectingState() {
+ initToConnectingState();
+ StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+ event.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_DISCONNECTED;
+ event.device = mTestDevice;
+ sendMessageAndVerifyTransition(
+ mHeadsetClientStateMachine.obtainMessage(StackEvent.STACK_EVENT, event),
+ HeadsetClientStateMachine.Disconnected.class);
+ verify(mHeadsetService).updateInbandRinging(eq(mTestDevice), eq(false));
+ }
+
+ @Test
+ public void testProcessStackEvent_ConnectionStateChanged_Connected_onConnectingState() {
+ initToConnectingState();
+ StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+ event.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_CONNECTED;
+ event.device = mTestDevice;
+ mHeadsetClientStateMachine.sendMessage(
+ mHeadsetClientStateMachine.obtainMessage(StackEvent.STACK_EVENT, event));
+ Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
+ IsInstanceOf.instanceOf(HeadsetClientStateMachine.Connecting.class));
+ }
+
+ @Test
+ public void testProcessStackEvent_ConnectionStateChanged_Connecting_onConnectingState() {
+ initToConnectingState();
+ StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+ event.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_CONNECTING;
+ event.device = mTestDevice;
+ mHeadsetClientStateMachine.sendMessage(
+ mHeadsetClientStateMachine.obtainMessage(StackEvent.STACK_EVENT, event));
+ Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
+ IsInstanceOf.instanceOf(HeadsetClientStateMachine.Connecting.class));
+ }
+
+ @Test
+ public void testProcessStackEvent_Call_onConnectingState() {
+ initToConnectingState();
+ StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CALL);
+ event.valueInt = StackEvent.EVENT_TYPE_CALL;
+ event.device = mTestDevice;
+ mHeadsetClientStateMachine.sendMessage(
+ mHeadsetClientStateMachine.obtainMessage(StackEvent.STACK_EVENT, event));
+ TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+ Assert.assertTrue(mHeadsetClientStateMachine.doesSuperHaveDeferredMessages(
+ StackEvent.STACK_EVENT));
+ }
+
+ @Test
+ public void testProcessStackEvent_CmdResultWithEmptyQueuedActions_onConnectingState() {
+ initToConnectingState();
+ StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CMD_RESULT);
+ event.valueInt = StackEvent.CMD_RESULT_TYPE_OK;
+ event.device = mTestDevice;
+ mHeadsetClientStateMachine.sendMessage(
+ mHeadsetClientStateMachine.obtainMessage(StackEvent.STACK_EVENT, event));
+ Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
+ IsInstanceOf.instanceOf(HeadsetClientStateMachine.Connecting.class));
+ }
+
+ @Test
+ public void testProcessStackEvent_Unknown_onConnectingState() {
+ String atCommand = "+ANDROID: 1";
+
+ initToConnectingState();
+ StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_UNKNOWN_EVENT);
+ event.valueString = atCommand;
+ event.device = mTestDevice;
+ mHeadsetClientStateMachine.sendMessage(
+ mHeadsetClientStateMachine.obtainMessage(StackEvent.STACK_EVENT, event));
+ TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+ Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
+ IsInstanceOf.instanceOf(HeadsetClientStateMachine.Connected.class));
+ verify(mHeadsetService).updateInbandRinging(eq(mTestDevice), eq(true));
+ }
+
+ @Test
+ public void testProcessConnectTimeoutMessage_onConnectingState() {
+ initToConnectingState();
+ Message msg = mHeadsetClientStateMachine
+ .obtainMessage(HeadsetClientStateMachine.CONNECTING_TIMEOUT);
+ sendMessageAndVerifyTransition(msg, HeadsetClientStateMachine.Disconnected.class);
+ verify(mHeadsetService).updateInbandRinging(eq(mTestDevice), eq(false));
+ }
+
+ @Test
+ public void testProcessConnectMessage_onConnectedState() {
+ initToConnectedState();
+ mHeadsetClientStateMachine.sendMessage(HeadsetClientStateMachine.CONNECT);
+ TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+ Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
+ IsInstanceOf.instanceOf(HeadsetClientStateMachine.Connected.class));
+ }
+
+ @Test
+ public void testProcessDisconnectMessage_onConnectedState() {
+ initToConnectedState();
+ mHeadsetClientStateMachine.sendMessage(HeadsetClientStateMachine.DISCONNECT, mTestDevice);
+ TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+ verify(mNativeInterface).disconnect(any(BluetoothDevice.class));
+ }
+
+ @Test
+ public void testProcessConnectAudioMessage_onConnectedState() {
+ initToConnectedState();
+ mHeadsetClientStateMachine.sendMessage(HeadsetClientStateMachine.CONNECT_AUDIO);
+ TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+ verify(mNativeInterface).connectAudio(any(BluetoothDevice.class));
+ }
+
+ @Test
+ public void testProcessDisconnectAudioMessage_onConnectedState() {
+ initToConnectedState();
+ mHeadsetClientStateMachine.sendMessage(HeadsetClientStateMachine.DISCONNECT_AUDIO);
+ TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+ verify(mNativeInterface).disconnectAudio(any(BluetoothDevice.class));
+ }
+
+ @Test
+ public void testProcessVoiceRecognitionStartMessage_onConnectedState() {
+ initToConnectedState();
+ mHeadsetClientStateMachine.sendMessage(HeadsetClientStateMachine.VOICE_RECOGNITION_START);
+ TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+ verify(mNativeInterface).startVoiceRecognition(any(BluetoothDevice.class));
+ }
+
+ @Test
+ public void testProcessDisconnectMessage_onAudioOnState() {
+ initToAudioOnState();
+ mHeadsetClientStateMachine.sendMessage(HeadsetClientStateMachine.DISCONNECT, mTestDevice);
+ TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+ Assert.assertTrue(mHeadsetClientStateMachine.doesSuperHaveDeferredMessages(
+ HeadsetClientStateMachine.DISCONNECT));
+ }
+
+ @Test
+ public void testProcessDisconnectAudioMessage_onAudioOnState() {
+ initToAudioOnState();
+ mHeadsetClientStateMachine.sendMessage(HeadsetClientStateMachine.DISCONNECT_AUDIO,
+ mTestDevice);
+ TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+ verify(mNativeInterface).disconnectAudio(any(BluetoothDevice.class));
+ }
+
+ @Test
+ public void testProcessHoldCall_onAudioOnState() {
+ initToAudioOnState();
+ HfpClientCall call = new HfpClientCall(mTestDevice, 0, HfpClientCall.CALL_STATE_ACTIVE,
+ "1", true, false, false);
+ mHeadsetClientStateMachine.mCalls.put(0, call);
+ int[] states = new int[1];
+ states[0] = HfpClientCall.CALL_STATE_ACTIVE;
+ mHeadsetClientStateMachine.sendMessage(HeadsetClientStateMachine.HOLD_CALL,
+ mTestDevice);
+ TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+ verify(mNativeInterface).handleCallAction(any(BluetoothDevice.class), anyInt(), eq(0));
+ }
+
+ @Test
+ public void testProcessStackEvent_ConnectionStateChanged_onAudioOnState() {
+ initToAudioOnState();
+ Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
+ IsInstanceOf.instanceOf(HeadsetClientStateMachine.AudioOn.class));
+ StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+ event.valueInt = HeadsetClientHalConstants.CONNECTION_STATE_DISCONNECTED;
+ event.device = mTestDevice;
+ mHeadsetClientStateMachine.sendMessage(
+ mHeadsetClientStateMachine.obtainMessage(StackEvent.STACK_EVENT, event));
+ TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+ Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
+ IsInstanceOf.instanceOf(HeadsetClientStateMachine.Disconnected.class));
+ verify(mHeadsetService).updateInbandRinging(eq(mTestDevice), eq(false));
+ }
+
+ @Test
+ public void testProcessStackEvent_AudioStateChanged_onAudioOnState() {
+ initToAudioOnState();
+ Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
+ IsInstanceOf.instanceOf(HeadsetClientStateMachine.AudioOn.class));
+ StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED);
+ event.valueInt = HeadsetClientHalConstants.AUDIO_STATE_DISCONNECTED;
+ event.device = mTestDevice;
+ mHeadsetClientStateMachine.sendMessage(
+ mHeadsetClientStateMachine.obtainMessage(StackEvent.STACK_EVENT, event));
+ TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+ Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
+ IsInstanceOf.instanceOf(HeadsetClientStateMachine.Connected.class));
+ }
+
+ /**
+ * Allow/disallow connection to any device
+ *
+ * @param allow if true, connection is allowed
+ */
+ private void allowConnection(boolean allow) {
+ mHeadsetClientStateMachine.allowConnect = allow;
+ }
+
+ private void initToConnectingState() {
+ doReturn(true).when(mNativeInterface).connect(any(BluetoothDevice.class));
+ sendMessageAndVerifyTransition(
+ mHeadsetClientStateMachine
+ .obtainMessage(HeadsetClientStateMachine.CONNECT, mTestDevice),
+ HeadsetClientStateMachine.Connecting.class);
+ }
+
+ private void initToConnectedState() {
+ String atCommand = "+ANDROID: 1";
+ initToConnectingState();
+ StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_UNKNOWN_EVENT);
+ event.valueString = atCommand;
+ event.device = mTestDevice;
+ mHeadsetClientStateMachine.sendMessage(
+ mHeadsetClientStateMachine.obtainMessage(StackEvent.STACK_EVENT, event));
+ TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+ Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
+ IsInstanceOf.instanceOf(HeadsetClientStateMachine.Connected.class));
+ verify(mHeadsetService).updateInbandRinging(eq(mTestDevice), eq(true));
+ }
+
+ private void initToAudioOnState() {
+ mHeadsetClientStateMachine.setAudioRouteAllowed(true);
+ initToConnectedState();
+ StackEvent event = new StackEvent(StackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED);
+ event.valueInt = HeadsetClientHalConstants.AUDIO_STATE_CONNECTED;
+ event.device = mTestDevice;
+ mHeadsetClientStateMachine.sendMessage(
+ mHeadsetClientStateMachine.obtainMessage(StackEvent.STACK_EVENT, event));
+ TestUtils.waitForLooperToFinishScheduledTask(mHandlerThread.getLooper());
+ Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
+ IsInstanceOf.instanceOf(HeadsetClientStateMachine.AudioOn.class));
+ }
+
+ private <T> void sendMessageAndVerifyTransition(Message msg, Class<T> type) {
+ Mockito.clearInvocations(mHeadsetClientService);
+ mHeadsetClientStateMachine.sendMessage(msg);
+ // Verify that one connection state broadcast is executed
+ verify(mHeadsetClientService, timeout(TIMEOUT_MS)).sendBroadcastMultiplePermissions(
+ any(Intent.class), any(String[].class), any(BroadcastOptions.class)
+ );
+ Assert.assertThat(mHeadsetClientStateMachine.getCurrentState(),
+ IsInstanceOf.instanceOf(type));
+ }
+
+ public static class TestHeadsetClientStateMachine extends HeadsetClientStateMachine {
+
+ Boolean allowConnect = null;
+
+ TestHeadsetClientStateMachine(HeadsetClientService context, HeadsetService headsetService,
+ Looper looper, NativeInterface nativeInterface) {
+ super(context, headsetService, looper, nativeInterface);
+ }
+
+ public boolean doesSuperHaveDeferredMessages(int what) {
+ return super.hasDeferredMessages(what);
+ }
+
+ @Override
+ public boolean okToConnect(BluetoothDevice device) {
+ return allowConnect != null ? allowConnect : super.okToConnect(device);
+ }
+ }
}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioServiceTest.java
index d2c7cd1..39c3cd7 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioServiceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioServiceTest.java
@@ -643,6 +643,9 @@
doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class));
doReturn(true).when(mNativeInterface).disconnectLeAudio(any(BluetoothDevice.class));
+ // Create device descriptor with connect request
+ assertWithMessage("Connect failed").that(mService.connect(mLeftDevice)).isTrue();
+
// Le Audio stack event: CONNECTION_STATE_CONNECTING - state machine should be created
generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_CONNECTING,
BluetoothProfile.STATE_DISCONNECTED);
@@ -659,9 +662,15 @@
mService.bondStateChanged(mLeftDevice, BluetoothDevice.BOND_NONE);
assertThat(mService.getDevices().contains(mLeftDevice)).isFalse();
+ // Remove bond will remove also device descriptor. Device has to be connected again
+ assertWithMessage("Connect failed").that(mService.connect(mLeftDevice)).isTrue();
+ verifyConnectionStateIntent(LeAudioStateMachine.sConnectTimeoutMs * 2,
+ mLeftDevice, BluetoothProfile.STATE_CONNECTING,
+ BluetoothProfile.STATE_DISCONNECTED);
+
// stack event: CONNECTION_STATE_CONNECTED - state machine should be created
generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_CONNECTED,
- BluetoothProfile.STATE_DISCONNECTED);
+ BluetoothProfile.STATE_CONNECTING);
assertThat(BluetoothProfile.STATE_CONNECTED)
.isEqualTo(mService.getConnectionState(mLeftDevice));
assertThat(mService.getDevices().contains(mLeftDevice)).isTrue();
@@ -710,6 +719,9 @@
doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class));
doReturn(true).when(mNativeInterface).disconnectLeAudio(any(BluetoothDevice.class));
+ // Create device descriptor with connect request
+ assertWithMessage("Connect failed").that(mService.connect(mLeftDevice)).isTrue();
+
// LeAudio stack event: CONNECTION_STATE_CONNECTING - state machine should be created
generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_CONNECTING,
BluetoothProfile.STATE_DISCONNECTED);
@@ -777,6 +789,9 @@
doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class));
doReturn(true).when(mNativeInterface).disconnectLeAudio(any(BluetoothDevice.class));
+ // Create device descriptor with connect request
+ assertWithMessage("Connect failed").that(mService.connect(mLeftDevice)).isTrue();
+
// LeAudio stack event: CONNECTION_STATE_CONNECTING - state machine should be created
generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_CONNECTING,
BluetoothProfile.STATE_DISCONNECTED);
@@ -1514,6 +1529,9 @@
@Test
public void testGetAudioLocation() {
+ doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class));
+ connectTestDevice(mSingleDevice, testGroupId);
+
assertThat(mService.getAudioLocation(null))
.isEqualTo(BluetoothLeAudio.AUDIO_LOCATION_INVALID);
@@ -1619,6 +1637,10 @@
public void testAuthorizeMcpServiceOnBluetoothEnableAndNodeRemoval() {
int groupId = 1;
+ doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class));
+ connectTestDevice(mLeftDevice, groupId);
+ connectTestDevice(mRightDevice, groupId);
+
generateGroupNodeAdded(mLeftDevice, groupId);
generateGroupNodeAdded(mRightDevice, groupId);
diff --git a/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapContentObserverTest.java b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapContentObserverTest.java
index d57aca6..b0f6bbe 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapContentObserverTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapContentObserverTest.java
@@ -18,9 +18,11 @@
import static org.mockito.Mockito.*;
+import android.app.Activity;
import android.content.ContentProviderClient;
import android.content.ContentValues;
import android.content.Context;
+import android.content.Intent;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.database.sqlite.SQLiteException;
@@ -29,6 +31,7 @@
import android.os.Looper;
import android.os.RemoteException;
import android.os.UserManager;
+import android.provider.ContactsContract;
import android.provider.Telephony;
import android.provider.Telephony.Mms;
import android.provider.Telephony.Sms;
@@ -43,6 +46,7 @@
import com.android.bluetooth.SignedLongLong;
import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
import com.android.bluetooth.mapapi.BluetoothMapContract;
+import com.android.bluetooth.mapapi.BluetoothMapContract.MessageColumns;
import com.android.obex.ResponseCodes;
import com.google.android.mms.pdu.PduHeaders;
@@ -60,9 +64,11 @@
import java.io.IOException;
import java.text.SimpleDateFormat;
+import java.util.Calendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
+import java.util.concurrent.TimeUnit;
@MediumTest
@RunWith(AndroidJUnit4.class)
@@ -72,7 +78,7 @@
static final int TEST_ID = 1;
static final long TEST_HANDLE_ONE = 1;
static final long TEST_HANDLE_TWO = 2;
- static final String TEST_URI_STR = "test_uri_str";
+ static final String TEST_URI_STR = "http://www.google.com";
static final int TEST_STATUS_VALUE = 1;
static final int TEST_THREAD_ID = 1;
static final long TEST_OLD_THREAD_ID = 2;
@@ -85,12 +91,32 @@
static final long TEST_OLD_FOLDER_ID = 6;
static final int TEST_READ_FLAG_ONE = 1;
static final int TEST_READ_FLAG_ZERO = 0;
- static final long TEST_DATE = 1;
+ static final long TEST_DATE_MS = Calendar.getInstance().getTimeInMillis();
+ static final long TEST_DATE_SEC = TimeUnit.MILLISECONDS.toSeconds(TEST_DATE_MS);
static final String TEST_SUBJECT = "subject";
static final int TEST_MMS_MTYPE = 1;
static final int TEST_MMS_TYPE_ALL = Telephony.BaseMmsColumns.MESSAGE_BOX_ALL;
static final int TEST_MMS_TYPE_INBOX = Telephony.BaseMmsColumns.MESSAGE_BOX_INBOX;
+ static final int TEST_SMS_TYPE_ALL = Telephony.TextBasedSmsColumns.MESSAGE_TYPE_ALL;
+ static final int TEST_SMS_TYPE_INBOX = Telephony.BaseMmsColumns.MESSAGE_BOX_INBOX;
static final Uri TEST_URI = Mms.CONTENT_URI;
+ static final String TEST_AUTHORITY = "test_authority";
+
+ static final long TEST_CONVO_ID = 1;
+ static final String TEST_NAME = "col_name";
+ static final String TEST_DISPLAY_NAME = "col_nickname";
+ static final String TEST_BT_UID = "1111";
+ static final int TEST_CHAT_STATE = 1;
+ static final int TEST_CHAT_STATE_DIFFERENT = 2;
+ static final String TEST_UCI = "col_uci";
+ static final String TEST_UCI_DIFFERENT = "col_uci_different";
+ static final long TEST_LAST_ACTIVITY = 1;
+ static final int TEST_PRESENCE_STATE = 1;
+ static final int TEST_PRESENCE_STATE_DIFFERENT = 2;
+ static final String TEST_STATUS_TEXT = "col_status_text";
+ static final String TEST_STATUS_TEXT_DIFFERENT = "col_status_text_different";
+ static final int TEST_PRIORITY = 1;
+ static final int TEST_LAST_ONLINE = 1;
@Mock
private BluetoothMnsObexClient mClient;
@@ -106,6 +132,8 @@
private ContentProviderClient mProviderClient;
@Mock
private BluetoothMapAccountItem mItem;
+ @Mock
+ private Intent mIntent;
@Spy
private BluetoothMethodProxy mMapMethodProxy = BluetoothMethodProxy.getInstance();
@@ -801,21 +829,75 @@
}
@Test
- public void initContactsList() throws Exception {
- long convoId = 1;
- String name = "col_name";
- String displayName = "col_nickname";
- String btUid = "1111";
- int chatState = 1;
- String uci = "col_uci";
- long lastActivity = 1;
- int presenceState = 1;
- String statusText = "col_status_text";
- int priority = 1;
- int lastOnline = 1;
+ public void initMsgList_withMsgSms() throws Exception {
+ MatrixCursor cursor = new MatrixCursor(new String[] {Sms._ID, Sms.TYPE, Sms.THREAD_ID,
+ Sms.READ});
+ cursor.addRow(new Object[] {(long) TEST_ID, TEST_SMS_TYPE_ALL, TEST_THREAD_ID,
+ TEST_READ_FLAG_ONE});
+ doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+ eq(BluetoothMapContentObserver.SMS_PROJECTION_SHORT), any(), any(), any());
+ cursor.moveToFirst();
+ Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
+ mObserver.setMsgListMsg(map, true);
+ mObserver.initMsgList();
+
+ BluetoothMapContentObserver.Msg msg = mObserver.getMsgListSms().get((long) TEST_ID);
+ Assert.assertEquals(msg.id, TEST_ID);
+ Assert.assertEquals(msg.type, TEST_SMS_TYPE_ALL);
+ Assert.assertEquals(msg.threadId, TEST_THREAD_ID);
+ Assert.assertEquals(msg.flagRead, TEST_READ_FLAG_ONE);
+ }
+
+ @Test
+ public void initMsgList_withMsgMms() throws Exception {
+ MatrixCursor cursor = new MatrixCursor(new String[] {Mms._ID, Mms.MESSAGE_BOX,
+ Mms.THREAD_ID, Mms.READ});
+ cursor.addRow(new Object[] {(long) TEST_ID, TEST_MMS_TYPE_ALL, TEST_THREAD_ID,
+ TEST_READ_FLAG_ZERO});
+ doReturn(null).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+ eq(BluetoothMapContentObserver.SMS_PROJECTION_SHORT), any(), any(), any());
+ doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+ eq(BluetoothMapContentObserver.MMS_PROJECTION_SHORT), any(), any(), any());
+ cursor.moveToFirst();
+ Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
+ mObserver.setMsgListMsg(map, true);
+
+ mObserver.initMsgList();
+
+ BluetoothMapContentObserver.Msg msg = mObserver.getMsgListMms().get((long) TEST_ID);
+ Assert.assertEquals(msg.id, TEST_ID);
+ Assert.assertEquals(msg.type, TEST_MMS_TYPE_ALL);
+ Assert.assertEquals(msg.threadId, TEST_THREAD_ID);
+ Assert.assertEquals(msg.flagRead, TEST_READ_FLAG_ZERO);
+ }
+
+ @Test
+ public void initMsgList_withMsg() throws Exception {
+ MatrixCursor cursor = new MatrixCursor(new String[] {MessageColumns._ID,
+ MessageColumns.FOLDER_ID, MessageColumns.FLAG_READ});
+ cursor.addRow(new Object[] {(long) TEST_ID, TEST_INBOX_FOLDER_ID, TEST_READ_FLAG_ONE});
+ doReturn(null).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+ eq(BluetoothMapContentObserver.SMS_PROJECTION_SHORT), any(), any(), any());
+ doReturn(null).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+ eq(BluetoothMapContentObserver.MMS_PROJECTION_SHORT), any(), any(), any());
+ when(mProviderClient.query(any(), any(), any(), any(), any())).thenReturn(cursor);
+ cursor.moveToFirst();
+ Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
+ mObserver.setMsgListMsg(map, true);
+
+ mObserver.initMsgList();
+
+ BluetoothMapContentObserver.Msg msg = mObserver.getMsgListMsg().get((long) TEST_ID);
+ Assert.assertEquals(msg.id, TEST_ID);
+ Assert.assertEquals(msg.folderId, TEST_INBOX_FOLDER_ID);
+ Assert.assertEquals(msg.flagRead, TEST_READ_FLAG_ONE);
+ }
+
+ @Test
+ public void initContactsList() throws Exception {
MatrixCursor cursor = new MatrixCursor(
- new String[]{BluetoothMapContract.ConvoContactColumns.CONVO_ID,
+ new String[] {BluetoothMapContract.ConvoContactColumns.CONVO_ID,
BluetoothMapContract.ConvoContactColumns.NAME,
BluetoothMapContract.ConvoContactColumns.NICKNAME,
BluetoothMapContract.ConvoContactColumns.X_BT_UID,
@@ -826,8 +908,9 @@
BluetoothMapContract.ConvoContactColumns.STATUS_TEXT,
BluetoothMapContract.ConvoContactColumns.PRIORITY,
BluetoothMapContract.ConvoContactColumns.LAST_ONLINE});
- cursor.addRow(new Object[] {convoId, name, displayName, btUid, chatState, uci, lastActivity,
- presenceState, statusText, priority, lastOnline});
+ cursor.addRow(new Object[] {TEST_CONVO_ID, TEST_NAME, TEST_DISPLAY_NAME, TEST_BT_UID,
+ TEST_CHAT_STATE, TEST_UCI, TEST_LAST_ACTIVITY, TEST_PRESENCE_STATE,
+ TEST_STATUS_TEXT, TEST_PRIORITY, TEST_LAST_ONLINE});
doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(), any(), any(),
any(), any());
@@ -837,22 +920,23 @@
Map<String, BluetoothMapConvoContactElement> map = new HashMap<>();
mObserver.setContactList(map, true);
mObserver.initContactsList();
- BluetoothMapConvoContactElement contactElement = mObserver.getContactList().get(uci);
+ BluetoothMapConvoContactElement contactElement = mObserver.getContactList().get(TEST_UCI);
final SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
- Assert.assertEquals(contactElement.getContactId(), uci);
- Assert.assertEquals(contactElement.getName(), name);
- Assert.assertEquals(contactElement.getDisplayName(), displayName);
- Assert.assertEquals(contactElement.getBtUid(), btUid);
- Assert.assertEquals(contactElement.getChatState(), chatState);
- Assert.assertEquals(contactElement.getPresenceStatus(), statusText);
- Assert.assertEquals(contactElement.getPresenceAvailability(), presenceState);
- Assert.assertEquals(contactElement.getLastActivityString(), format.format(lastActivity));
- Assert.assertEquals(contactElement.getPriority(), priority);
+ Assert.assertEquals(contactElement.getContactId(), TEST_UCI);
+ Assert.assertEquals(contactElement.getName(), TEST_NAME);
+ Assert.assertEquals(contactElement.getDisplayName(), TEST_DISPLAY_NAME);
+ Assert.assertEquals(contactElement.getBtUid(), TEST_BT_UID);
+ Assert.assertEquals(contactElement.getChatState(), TEST_CHAT_STATE);
+ Assert.assertEquals(contactElement.getPresenceStatus(), TEST_STATUS_TEXT);
+ Assert.assertEquals(contactElement.getPresenceAvailability(), TEST_PRESENCE_STATE);
+ Assert.assertEquals(contactElement.getLastActivityString(), format.format(
+ TEST_LAST_ACTIVITY));
+ Assert.assertEquals(contactElement.getPriority(), TEST_PRIORITY);
}
@Test
- public void handleMsgListChangesMsg_withNonExistingMessage_andVersionEleven() throws Exception {
+ public void handleMsgListChangesMsg_withNonExistingMessage_andVersion11() throws Exception {
MatrixCursor cursor = new MatrixCursor(new String[] {
BluetoothMapContract.MessageColumns._ID,
BluetoothMapContract.MessageColumns.FOLDER_ID,
@@ -862,7 +946,7 @@
BluetoothMapContract.MessageColumns.FROM_LIST,
BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY});
cursor.addRow(new Object[] {TEST_HANDLE_ONE, TEST_INBOX_FOLDER_ID, TEST_READ_FLAG_ONE,
- TEST_DATE, TEST_SUBJECT, TEST_ADDRESS, 1});
+ TEST_DATE_MS, TEST_SUBJECT, TEST_ADDRESS, 1});
when(mProviderClient.query(any(), any(), any(), any(), any())).thenReturn(cursor);
Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
@@ -888,7 +972,7 @@
}
@Test
- public void handleMsgListChangesMsg_withNonExistingMessage_andVersionTwelve() throws Exception {
+ public void handleMsgListChangesMsg_withNonExistingMessage_andVersion12() throws Exception {
MatrixCursor cursor = new MatrixCursor(new String[] {
BluetoothMapContract.MessageColumns._ID,
BluetoothMapContract.MessageColumns.FOLDER_ID,
@@ -900,7 +984,7 @@
BluetoothMapContract.MessageColumns.THREAD_ID,
BluetoothMapContract.MessageColumns.THREAD_NAME});
cursor.addRow(new Object[] {TEST_HANDLE_ONE, TEST_INBOX_FOLDER_ID, TEST_READ_FLAG_ONE,
- TEST_DATE, TEST_SUBJECT, TEST_ADDRESS, 1, 1, "threadName"});
+ TEST_DATE_MS, TEST_SUBJECT, TEST_ADDRESS, 1, 1, "threadName"});
when(mProviderClient.query(any(), any(), any(), any(), any())).thenReturn(cursor);
Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
@@ -924,7 +1008,7 @@
}
@Test
- public void handleMsgListChangesMsg_withNonExistingMessage_andVersionTen() throws Exception {
+ public void handleMsgListChangesMsg_withNonExistingMessage_andVersion10() throws Exception {
MatrixCursor cursor = new MatrixCursor(new String[] {
BluetoothMapContract.MessageColumns._ID,
BluetoothMapContract.MessageColumns.FOLDER_ID,
@@ -1088,12 +1172,12 @@
}
@Test
- public void handleMsgListChangesMms_withNonExistingMessage_andVersionEleven() {
+ public void handleMsgListChangesMms_withNonExistingMessage_andVersion11() {
MatrixCursor cursor = new MatrixCursor(new String[] {Mms._ID, Mms.MESSAGE_BOX,
Mms.MESSAGE_TYPE, Mms.THREAD_ID, Mms.READ, Mms.DATE, Mms.SUBJECT,
- Mms.PRIORITY, Mms.Addr.ADDRESS});
+ Mms.PRIORITY, Mms.Addr.ADDRESS});
cursor.addRow(new Object[] {TEST_HANDLE_ONE, TEST_MMS_TYPE_ALL, TEST_MMS_MTYPE,
- TEST_THREAD_ID, TEST_READ_FLAG_ONE, TEST_DATE, TEST_SUBJECT,
+ TEST_THREAD_ID, TEST_READ_FLAG_ONE, TEST_DATE_SEC, TEST_SUBJECT,
PduHeaders.PRIORITY_HIGH, null});
doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(), any(), any(),
any(), any());
@@ -1118,12 +1202,12 @@
}
@Test
- public void handleMsgListChangesMms_withNonExistingMessage_andVersionTwelve() {
+ public void handleMsgListChangesMms_withNonExistingMessage_andVersion12() {
MatrixCursor cursor = new MatrixCursor(new String[] {Mms._ID, Mms.MESSAGE_BOX,
Mms.MESSAGE_TYPE, Mms.THREAD_ID, Mms.READ, Mms.DATE, Mms.SUBJECT,
Mms.PRIORITY, Mms.Addr.ADDRESS});
cursor.addRow(new Object[] {TEST_HANDLE_ONE, TEST_MMS_TYPE_ALL, TEST_MMS_MTYPE,
- TEST_THREAD_ID, TEST_READ_FLAG_ONE, TEST_DATE, TEST_SUBJECT,
+ TEST_THREAD_ID, TEST_READ_FLAG_ONE, TEST_DATE_SEC, TEST_SUBJECT,
PduHeaders.PRIORITY_HIGH, null});
doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(), any(), any(),
any(), any());
@@ -1148,7 +1232,37 @@
}
@Test
- public void handleMsgListChangesMms_withNonExistingMessage_andVersionTen() {
+ public void handleMsgListChangesMms_withNonExistingOldMessage_andVersion12() {
+ Calendar cal = Calendar.getInstance();
+ cal.add(Calendar.YEAR, -1);
+ cal.add(Calendar.DATE, -1);
+ long timestampSec = TimeUnit.MILLISECONDS.toSeconds(cal.getTimeInMillis());
+
+ MatrixCursor cursor = new MatrixCursor(new String[] {Mms._ID, Mms.MESSAGE_BOX,
+ Mms.MESSAGE_TYPE, Mms.THREAD_ID, Mms.READ, Mms.DATE, Mms.SUBJECT,
+ Mms.PRIORITY, Mms.Addr.ADDRESS});
+ cursor.addRow(new Object[] {TEST_HANDLE_ONE, TEST_MMS_TYPE_ALL, TEST_MMS_MTYPE,
+ TEST_THREAD_ID, TEST_READ_FLAG_ONE, timestampSec, TEST_SUBJECT,
+ PduHeaders.PRIORITY_HIGH, null});
+ doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(), any(), any(),
+ any(), any());
+
+ Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
+ // Giving a different handle for msg below and cursor above makes handleMsgListChangesMms()
+ // function for a non-existing message
+ BluetoothMapContentObserver.Msg msg = new BluetoothMapContentObserver.Msg(TEST_HANDLE_TWO,
+ TEST_INBOX_FOLDER_ID, TEST_READ_FLAG_ONE);
+ map.put(TEST_HANDLE_TWO, msg);
+ mObserver.setMsgListMms(map, true);
+ mObserver.mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V12;
+
+ mObserver.handleMsgListChangesMms();
+
+ Assert.assertEquals(mObserver.getMsgListMms().get(TEST_HANDLE_ONE), null);
+ }
+
+ @Test
+ public void handleMsgListChangesMms_withNonExistingMessage_andVersion10() {
MatrixCursor cursor = new MatrixCursor(new String[] {Mms._ID, Mms.MESSAGE_BOX,
Mms.MESSAGE_TYPE, Mms.THREAD_ID, Mms.READ});
cursor.addRow(new Object[] {TEST_HANDLE_ONE, TEST_MMS_TYPE_ALL, TEST_MMS_MTYPE,
@@ -1292,6 +1406,539 @@
TEST_READ_FLAG_ONE);
}
+ @Test
+ public void handleMsgListChangesSms_withNonExistingMessage_andVersion11() {
+ MatrixCursor cursor = new MatrixCursor(new String[] {Sms._ID, Sms.TYPE, Sms.THREAD_ID,
+ Sms.READ, Sms.DATE, Sms.BODY, Sms.ADDRESS, ContactsContract.Contacts.DISPLAY_NAME});
+ cursor.addRow(new Object[] {TEST_HANDLE_ONE, TEST_SMS_TYPE_INBOX, TEST_THREAD_ID,
+ TEST_READ_FLAG_ONE, TEST_DATE_MS, TEST_SUBJECT, TEST_ADDRESS, null});
+ doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(), any(), any(),
+ any(), any());
+
+ Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
+ // Giving a different handle for msg below and cursor above makes handleMsgListChangesSms()
+ // function for a non-existing message
+ BluetoothMapContentObserver.Msg msg = new BluetoothMapContentObserver.Msg(TEST_HANDLE_TWO,
+ TEST_SMS_TYPE_ALL, TEST_READ_FLAG_ONE);
+ map.put(TEST_HANDLE_TWO, msg);
+ mObserver.setMsgListSms(map, true);
+ mObserver.mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V11;
+
+ mObserver.handleMsgListChangesSms();
+
+ Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).id, TEST_HANDLE_ONE);
+ Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).type,
+ TEST_SMS_TYPE_INBOX);
+ Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).threadId,
+ TEST_THREAD_ID);
+ Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).flagRead,
+ TEST_READ_FLAG_ONE);
+ }
+
+ @Test
+ public void handleMsgListChangesSms_withNonExistingMessage_andVersion12() {
+ MatrixCursor cursor = new MatrixCursor(new String[] {Sms._ID, Sms.TYPE, Sms.THREAD_ID,
+ Sms.READ, Sms.DATE, Sms.BODY, Sms.ADDRESS});
+ cursor.addRow(new Object[] {TEST_HANDLE_ONE, TEST_SMS_TYPE_ALL, TEST_THREAD_ID,
+ TEST_READ_FLAG_ONE, TEST_DATE_MS, "", null});
+ doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(), any(), any(),
+ any(), any());
+
+ Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
+ // Giving a different handle for msg below and cursor above makes handleMsgListChangesSms()
+ // function for a non-existing message
+ BluetoothMapContentObserver.Msg msg = new BluetoothMapContentObserver.Msg(TEST_HANDLE_TWO,
+ TEST_SMS_TYPE_INBOX, TEST_READ_FLAG_ONE);
+ map.put(TEST_HANDLE_TWO, msg);
+ mObserver.setMsgListSms(map, true);
+ mObserver.mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V12;
+
+ mObserver.handleMsgListChangesSms();
+
+ Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).id, TEST_HANDLE_ONE);
+ Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).type,
+ TEST_SMS_TYPE_ALL);
+ Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).threadId,
+ TEST_THREAD_ID);
+ Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).flagRead,
+ TEST_READ_FLAG_ONE);
+ }
+
+ @Test
+ public void handleMsgListChangesSms_withNonExistingOldMessage_andVersion12() {
+ Calendar cal = Calendar.getInstance();
+ cal.add(Calendar.YEAR, -1);
+ cal.add(Calendar.DATE, -1);
+
+ MatrixCursor cursor = new MatrixCursor(new String[] {Sms._ID, Sms.TYPE, Sms.THREAD_ID,
+ Sms.READ, Sms.DATE, Sms.BODY, Sms.ADDRESS});
+ cursor.addRow(new Object[] {TEST_HANDLE_ONE, TEST_SMS_TYPE_ALL, TEST_THREAD_ID,
+ TEST_READ_FLAG_ONE, cal.getTimeInMillis(), "", null});
+ doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(), any(), any(),
+ any(), any());
+
+ Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
+ // Giving a different handle for msg below and cursor above makes handleMsgListChangesMms()
+ // function for a non-existing message
+ BluetoothMapContentObserver.Msg msg = new BluetoothMapContentObserver.Msg(TEST_HANDLE_TWO,
+ TEST_SMS_TYPE_INBOX, TEST_READ_FLAG_ONE);
+ map.put(TEST_HANDLE_TWO, msg);
+ mObserver.setMsgListSms(map, true);
+ mObserver.mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V12;
+
+ mObserver.handleMsgListChangesSms();
+
+ Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE), null);
+ }
+
+ @Test
+ public void handleMsgListChangesSms_withNonExistingMessage_andVersion10() {
+ MatrixCursor cursor = new MatrixCursor(new String[] {Sms._ID, Sms.TYPE, Sms.THREAD_ID,
+ Sms.READ});
+ cursor.addRow(new Object[] {TEST_HANDLE_ONE, TEST_SMS_TYPE_ALL, TEST_THREAD_ID,
+ TEST_READ_FLAG_ONE});
+ doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(), any(), any(),
+ any(), any());
+
+ Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
+ // Giving a different handle for msg below and cursor above makes handleMsgListChangesSms()
+ // function for a non-existing message
+ BluetoothMapContentObserver.Msg msg = new BluetoothMapContentObserver.Msg(TEST_HANDLE_TWO,
+ TEST_SMS_TYPE_INBOX, TEST_READ_FLAG_ONE);
+ map.put(TEST_HANDLE_TWO, msg);
+ mObserver.setMsgListSms(map, true);
+ mObserver.mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V10;
+
+ mObserver.handleMsgListChangesSms();
+
+ Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).id, TEST_HANDLE_ONE);
+ Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).type,
+ TEST_SMS_TYPE_ALL);
+ Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).threadId,
+ TEST_THREAD_ID);
+ Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).flagRead,
+ TEST_READ_FLAG_ONE);
+ }
+
+ @Test
+ public void handleMsgListChangesSms_withExistingMessage_withNonEqualType() {
+ MatrixCursor cursor = new MatrixCursor(new String[] {Sms._ID, Sms.TYPE, Sms.THREAD_ID,
+ Sms.READ});
+ cursor.addRow(new Object[] {TEST_HANDLE_ONE, TEST_SMS_TYPE_ALL, TEST_THREAD_ID,
+ TEST_READ_FLAG_ONE});
+ doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(), any(), any(),
+ any(), any());
+
+ Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
+ // Giving the same handle for msg below and cursor above makes handleMsgListChangesSms()
+ // function for an existing message
+ BluetoothMapContentObserver.Msg msg = new BluetoothMapContentObserver.Msg(TEST_HANDLE_ONE,
+ TEST_SMS_TYPE_INBOX, TEST_THREAD_ID, TEST_READ_FLAG_ZERO);
+ map.put(TEST_HANDLE_ONE, msg);
+ mObserver.setMsgListSms(map, true);
+ mObserver.mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V12;
+
+ mObserver.handleMsgListChangesSms();
+
+ Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).id, TEST_HANDLE_ONE);
+ Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).type,
+ TEST_SMS_TYPE_ALL);
+ Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).threadId,
+ TEST_THREAD_ID);
+ Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).flagRead,
+ TEST_READ_FLAG_ONE);
+ }
+
+ @Test
+ public void handleMsgListChangesSms_withExistingMessage_withDeletedThreadId() {
+ MatrixCursor cursor = new MatrixCursor(new String[] {Sms._ID, Sms.TYPE, Sms.THREAD_ID,
+ Sms.READ});
+ cursor.addRow(new Object[] {TEST_HANDLE_ONE, TEST_SMS_TYPE_ALL,
+ BluetoothMapContentObserver.DELETED_THREAD_ID, TEST_READ_FLAG_ONE});
+ doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(), any(), any(),
+ any(), any());
+
+ Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
+ // Giving the same handle for msg below and cursor above makes handleMsgListChangesSms()
+ // function for an existing message
+ BluetoothMapContentObserver.Msg msg = new BluetoothMapContentObserver.Msg(TEST_HANDLE_ONE,
+ TEST_SMS_TYPE_ALL, TEST_THREAD_ID, TEST_READ_FLAG_ZERO);
+ map.put(TEST_HANDLE_ONE, msg);
+ mObserver.setMsgListSms(map, true);
+ mObserver.mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V12;
+
+ mObserver.handleMsgListChangesSms();
+
+ Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).id, TEST_HANDLE_ONE);
+ Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).type, TEST_SMS_TYPE_ALL);
+ Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).threadId,
+ BluetoothMapContentObserver.DELETED_THREAD_ID);
+ Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).flagRead,
+ TEST_READ_FLAG_ONE);
+ }
+
+ @Test
+ public void handleMsgListChangesSms_withExistingMessage_withUndeletedThreadId() {
+ int undeletedThreadId = 0;
+ MatrixCursor cursor = new MatrixCursor(new String[] {Sms._ID, Sms.TYPE, Sms.THREAD_ID,
+ Sms.READ});
+ cursor.addRow(new Object[] {TEST_HANDLE_ONE, TEST_SMS_TYPE_ALL, undeletedThreadId,
+ TEST_READ_FLAG_ONE});
+ doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(), any(), any(),
+ any(), any());
+
+ Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
+ // Giving the same handle for msg below and cursor above makes handleMsgListChangesSms()
+ // function for an existing message
+ BluetoothMapContentObserver.Msg msg = new BluetoothMapContentObserver.Msg(TEST_HANDLE_ONE,
+ TEST_SMS_TYPE_ALL, TEST_THREAD_ID, TEST_READ_FLAG_ZERO);
+ map.put(TEST_HANDLE_ONE, msg);
+ mObserver.setMsgListSms(map, true);
+ mObserver.mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V12;
+
+ mObserver.handleMsgListChangesSms();
+
+ Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).id, TEST_HANDLE_ONE);
+ Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).type, TEST_SMS_TYPE_ALL);
+ Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).threadId,
+ undeletedThreadId);
+ Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).flagRead,
+ TEST_READ_FLAG_ONE);
+ }
+
+ @Test
+ public void handleMmsSendIntent_withMnsClientNotConnected() {
+ when(mClient.isConnected()).thenReturn(false);
+
+ Assert.assertFalse(mObserver.handleMmsSendIntent(mContext, mIntent));
+ }
+
+ @Test
+ public void handleMmsSendIntent_withInvalidHandle() {
+ when(mClient.isConnected()).thenReturn(true);
+ doReturn((long) -1).when(mIntent).getLongExtra(
+ BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_HANDLE, -1);
+
+ Assert.assertTrue(mObserver.handleMmsSendIntent(mContext, mIntent));
+ }
+
+ @Test
+ public void handleMmsSendIntent_withActivityResultOk() {
+ when(mClient.isConnected()).thenReturn(true);
+ doReturn(TEST_HANDLE_ONE).when(mIntent).getLongExtra(
+ BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_HANDLE, -1);
+ doReturn(Activity.RESULT_OK).when(mIntent).getIntExtra(
+ BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_RESULT, Activity.RESULT_CANCELED);
+ doReturn(0).when(mIntent).getIntExtra(
+ BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_TRANSPARENT, 0);
+ mObserver.mObserverRegistered = true;
+
+ Assert.assertTrue(mObserver.handleMmsSendIntent(mContext, mIntent));
+ }
+
+ @Test
+ public void handleMmsSendIntent_withActivityResultFirstUser() {
+ when(mClient.isConnected()).thenReturn(true);
+ doReturn(TEST_HANDLE_ONE).when(mIntent).getLongExtra(
+ BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_HANDLE, -1);
+ doReturn(Activity.RESULT_FIRST_USER).when(mIntent).getIntExtra(
+ BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_RESULT, Activity.RESULT_CANCELED);
+ mObserver.mObserverRegistered = true;
+ doReturn(TEST_PLACEHOLDER_INT).when(mMapMethodProxy).contentResolverDelete(any(), any(),
+ any(), any());
+
+ Assert.assertTrue(mObserver.handleMmsSendIntent(mContext, mIntent));
+ }
+
+ @Test
+ public void actionMessageSentDisconnected_withTypeMms() {
+ Map<Long, BluetoothMapContentObserver.Msg> mmsMsgList = new HashMap<>();
+ BluetoothMapContentObserver.Msg msg = createSimpleMsg();
+ mmsMsgList.put(TEST_HANDLE_ONE, msg);
+ doReturn(1).when(mIntent).getIntExtra(
+ BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_TRANSPARENT, 0);
+ doReturn((long) -1).when(mIntent).getLongExtra(
+ BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_HANDLE, -1);
+ // This mock sets type to MMS
+ doReturn(4).when(mIntent).getIntExtra(
+ BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_MSG_TYPE, TYPE.NONE.ordinal());
+
+ mObserver.actionMessageSentDisconnected(mContext, mIntent, 1);
+
+ Assert.assertTrue(mmsMsgList.containsKey(TEST_HANDLE_ONE));
+ }
+
+ @Test
+ public void actionMessageSentDisconnected_withTypeEmail() {
+ // This sets to null uriString
+ doReturn(null).when(mIntent).getStringExtra(
+ BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_URI);
+ doReturn(1).when(mIntent).getIntExtra(
+ BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_TRANSPARENT, 0);
+ // This mock sets type to Email
+ doReturn(1).when(mIntent).getIntExtra(
+ BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_MSG_TYPE, TYPE.NONE.ordinal());
+ clearInvocations(mContext);
+
+ mObserver.actionMessageSentDisconnected(mContext, mIntent, Activity.RESULT_FIRST_USER);
+
+ verify(mContext, never()).getContentResolver();
+ }
+
+ @Test
+ public void actionMmsSent_withInvalidHandle() {
+ Map<Long, BluetoothMapContentObserver.Msg> mmsMsgList = new HashMap<>();
+ BluetoothMapContentObserver.Msg msg = createSimpleMsg();
+ mmsMsgList.put(TEST_HANDLE_ONE, msg);
+ doReturn(1).when(mIntent).getIntExtra(
+ BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_TRANSPARENT, 0);
+ doReturn((long) -1).when(mIntent).getLongExtra(
+ BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_HANDLE, -1);
+
+ mObserver.actionMmsSent(mContext, mIntent, 1, mmsMsgList);
+
+ Assert.assertTrue(mmsMsgList.containsKey(TEST_HANDLE_ONE));
+ }
+
+ @Test
+ public void actionMmsSent_withTransparency() {
+ Map<Long, BluetoothMapContentObserver.Msg> mmsMsgList = new HashMap<>();
+ BluetoothMapContentObserver.Msg msg = createSimpleMsg();
+ mmsMsgList.put(TEST_HANDLE_ONE, msg);
+ // This mock turns on the transparent flag
+ doReturn(1).when(mIntent).getIntExtra(
+ BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_TRANSPARENT, 0);
+ doReturn(TEST_HANDLE_ONE).when(mIntent).getLongExtra(
+ BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_HANDLE, -1);
+ doReturn(TEST_PLACEHOLDER_INT).when(mMapMethodProxy).contentResolverDelete(any(), any(),
+ any(), any());
+
+ mObserver.actionMmsSent(mContext, mIntent, 1, mmsMsgList);
+
+ Assert.assertFalse(mmsMsgList.containsKey(TEST_HANDLE_ONE));
+ }
+
+ @Test
+ public void actionMmsSent_withActivityResultOk() {
+ Map<Long, BluetoothMapContentObserver.Msg> mmsMsgList = new HashMap<>();
+ BluetoothMapContentObserver.Msg msg = createSimpleMsg();
+ mmsMsgList.put(TEST_HANDLE_ONE, msg);
+ // This mock turns off the transparent flag
+ doReturn(0).when(mIntent).getIntExtra(
+ BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_TRANSPARENT, 0);
+ doReturn(TEST_HANDLE_ONE).when(mIntent).getLongExtra(
+ BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_HANDLE, -1);
+
+ MatrixCursor cursor = new MatrixCursor(new String[] {});
+ doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(), any(), any(),
+ any(), any());
+ doReturn(TEST_PLACEHOLDER_INT).when(mMapMethodProxy).contentResolverUpdate(any(), any(),
+ any(), any(), any());
+
+ mObserver.actionMmsSent(mContext, mIntent, Activity.RESULT_OK, mmsMsgList);
+
+ Assert.assertTrue(mmsMsgList.containsKey(TEST_HANDLE_ONE));
+ }
+
+ @Test
+ public void actionMmsSent_withActivityResultFirstUser() {
+ Map<Long, BluetoothMapContentObserver.Msg> mmsMsgList = new HashMap<>();
+ BluetoothMapContentObserver.Msg msg = createSimpleMsg();
+ mmsMsgList.put(TEST_HANDLE_ONE, msg);
+ // This mock turns off the transparent flag
+ doReturn(0).when(mIntent).getIntExtra(
+ BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_TRANSPARENT, 0);
+ doReturn(TEST_HANDLE_ONE).when(mIntent).getLongExtra(
+ BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_HANDLE, -1);
+
+ mObserver.actionMmsSent(mContext, mIntent, Activity.RESULT_FIRST_USER, mmsMsgList);
+
+ Assert.assertEquals(msg.type, Mms.MESSAGE_BOX_OUTBOX);
+ }
+
+ @Test
+ public void actionSmsSentDisconnected_withNullUriString() {
+ // This sets to null uriString
+ doReturn(null).when(mIntent).getStringExtra(
+ BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_URI);
+ doReturn(1).when(mIntent).getIntExtra(
+ BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_TRANSPARENT, 0);
+
+ clearInvocations(mContext);
+ mObserver.actionSmsSentDisconnected(mContext, mIntent, Activity.RESULT_FIRST_USER);
+
+ verify(mContext, never()).getContentResolver();
+ }
+
+ @Test
+ public void actionSmsSentDisconnected_withActivityResultOk_andTransparentOff() {
+ doReturn(TEST_URI_STR).when(mIntent).getStringExtra(
+ BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_URI);
+ // This mock turns off the transparent flag
+ doReturn(0).when(mIntent).getIntExtra(
+ BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_TRANSPARENT, 0);
+ doReturn(TEST_PLACEHOLDER_INT).when(mMapMethodProxy).contentResolverUpdate(any(), any(),
+ any(), any(), any());
+
+ clearInvocations(mContext);
+ mObserver.actionSmsSentDisconnected(mContext, mIntent, Activity.RESULT_OK);
+
+ verify(mContext).getContentResolver();
+ }
+
+ @Test
+ public void actionSmsSentDisconnected_withActivityResultOk_andTransparentOn() {
+ doReturn(TEST_URI_STR).when(mIntent).getStringExtra(
+ BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_URI);
+ // This mock turns on the transparent flag
+ doReturn(1).when(mIntent).getIntExtra(
+ BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_TRANSPARENT, 0);
+ doReturn(TEST_PLACEHOLDER_INT).when(mMapMethodProxy).contentResolverDelete(any(), any(),
+ any(), any());
+
+ clearInvocations(mContext);
+ mObserver.actionSmsSentDisconnected(mContext, mIntent, Activity.RESULT_OK);
+
+ verify(mContext).getContentResolver();
+ }
+
+ @Test
+ public void actionSmsSentDisconnected_withActivityResultFirstUser_andTransparentOff() {
+ doReturn(TEST_URI_STR).when(mIntent).getStringExtra(
+ BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_URI);
+ // This mock turns off the transparent flag
+ doReturn(0).when(mIntent).getIntExtra(
+ BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_TRANSPARENT, 0);
+ doReturn(TEST_PLACEHOLDER_INT).when(mMapMethodProxy).contentResolverUpdate(any(), any(),
+ any(), any(), any());
+
+ clearInvocations(mContext);
+ mObserver.actionSmsSentDisconnected(mContext, mIntent, Activity.RESULT_OK);
+
+ verify(mContext).getContentResolver();
+ }
+
+ @Test
+ public void actionSmsSentDisconnected_withActivityResultFirstUser_andTransparentOn() {
+ doReturn(TEST_URI_STR).when(mIntent).getStringExtra(
+ BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_URI);
+ // This mock turns on the transparent flag
+ doReturn(1).when(mIntent).getIntExtra(
+ BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_TRANSPARENT, 0);
+ doReturn(null).when(mContext).getContentResolver();
+
+ clearInvocations(mContext);
+ mObserver.actionSmsSentDisconnected(mContext, mIntent, Activity.RESULT_OK);
+
+ verify(mContext).getContentResolver();
+ }
+
+ @Test
+ public void handleContactListChanges_withNullContactForUci() throws Exception {
+ Uri uri = mock(Uri.class);
+ mObserver.mAuthority = TEST_AUTHORITY;
+ when(uri.getAuthority()).thenReturn(TEST_AUTHORITY);
+
+ MatrixCursor cursor = new MatrixCursor(
+ new String[]{BluetoothMapContract.ConvoContactColumns.CONVO_ID,
+ BluetoothMapContract.ConvoContactColumns.NAME,
+ BluetoothMapContract.ConvoContactColumns.NICKNAME,
+ BluetoothMapContract.ConvoContactColumns.X_BT_UID,
+ BluetoothMapContract.ConvoContactColumns.CHAT_STATE,
+ BluetoothMapContract.ConvoContactColumns.UCI,
+ BluetoothMapContract.ConvoContactColumns.LAST_ACTIVE,
+ BluetoothMapContract.ConvoContactColumns.PRESENCE_STATE,
+ BluetoothMapContract.ConvoContactColumns.STATUS_TEXT,
+ BluetoothMapContract.ConvoContactColumns.PRIORITY,
+ BluetoothMapContract.ConvoContactColumns.LAST_ONLINE});
+ cursor.addRow(new Object[] {TEST_CONVO_ID, TEST_NAME, TEST_DISPLAY_NAME, TEST_BT_UID,
+ TEST_CHAT_STATE, TEST_UCI, TEST_LAST_ACTIVITY, TEST_PRESENCE_STATE,
+ TEST_STATUS_TEXT, TEST_PRIORITY, TEST_LAST_ONLINE});
+ doReturn(cursor).when(mProviderClient).query(any(), any(), any(), any(), any());
+
+ Map<String, BluetoothMapConvoContactElement> map = new HashMap<>();
+ map.put(TEST_UCI_DIFFERENT, null);
+ mObserver.setContactList(map, true);
+ mObserver.mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V12;
+
+ mObserver.handleContactListChanges(uri);
+
+ BluetoothMapConvoContactElement contactElement = mObserver.getContactList().get(TEST_UCI);
+ final SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
+ Assert.assertEquals(contactElement.getContactId(), TEST_UCI);
+ Assert.assertEquals(contactElement.getName(), TEST_NAME);
+ Assert.assertEquals(contactElement.getDisplayName(), TEST_DISPLAY_NAME);
+ Assert.assertEquals(contactElement.getBtUid(), TEST_BT_UID);
+ Assert.assertEquals(contactElement.getChatState(), TEST_CHAT_STATE);
+ Assert.assertEquals(contactElement.getPresenceStatus(), TEST_STATUS_TEXT);
+ Assert.assertEquals(contactElement.getPresenceAvailability(), TEST_PRESENCE_STATE);
+ Assert.assertEquals(contactElement.getLastActivityString(), format.format(
+ TEST_LAST_ACTIVITY));
+ Assert.assertEquals(contactElement.getPriority(), TEST_PRIORITY);
+ }
+
+ @Test
+ public void handleContactListChanges_withNonNullContactForUci() throws Exception {
+ Uri uri = mock(Uri.class);
+ mObserver.mAuthority = TEST_AUTHORITY;
+ when(uri.getAuthority()).thenReturn(TEST_AUTHORITY);
+
+ MatrixCursor cursor = new MatrixCursor(
+ new String[]{BluetoothMapContract.ConvoContactColumns.CONVO_ID,
+ BluetoothMapContract.ConvoContactColumns.NAME,
+ BluetoothMapContract.ConvoContactColumns.NICKNAME,
+ BluetoothMapContract.ConvoContactColumns.X_BT_UID,
+ BluetoothMapContract.ConvoContactColumns.CHAT_STATE,
+ BluetoothMapContract.ConvoContactColumns.UCI,
+ BluetoothMapContract.ConvoContactColumns.LAST_ACTIVE,
+ BluetoothMapContract.ConvoContactColumns.PRESENCE_STATE,
+ BluetoothMapContract.ConvoContactColumns.STATUS_TEXT,
+ BluetoothMapContract.ConvoContactColumns.PRIORITY,
+ BluetoothMapContract.ConvoContactColumns.LAST_ONLINE});
+ cursor.addRow(new Object[] {TEST_CONVO_ID, TEST_NAME, TEST_DISPLAY_NAME, TEST_BT_UID,
+ TEST_CHAT_STATE, TEST_UCI, TEST_LAST_ACTIVITY, TEST_PRESENCE_STATE,
+ TEST_STATUS_TEXT, TEST_PRIORITY, TEST_LAST_ONLINE});
+ doReturn(cursor).when(mProviderClient).query(any(), any(), any(), any(), any());
+
+ Map<String, BluetoothMapConvoContactElement> map = new HashMap<>();
+ map.put(TEST_UCI_DIFFERENT, null);
+ BluetoothMapConvoContactElement contact = new BluetoothMapConvoContactElement(TEST_UCI,
+ TEST_NAME, TEST_DISPLAY_NAME, TEST_STATUS_TEXT_DIFFERENT,
+ TEST_PRESENCE_STATE_DIFFERENT, TEST_LAST_ACTIVITY, TEST_CHAT_STATE_DIFFERENT,
+ TEST_PRIORITY, TEST_BT_UID);
+ map.put(TEST_UCI, contact);
+ mObserver.setContactList(map, true);
+ mObserver.mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V12;
+ when(mTelephonyManager.getLine1Number()).thenReturn("");
+
+ mObserver.handleContactListChanges(uri);
+
+ BluetoothMapConvoContactElement contactElement = mObserver.getContactList().get(TEST_UCI);
+ final SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
+ Assert.assertEquals(contactElement.getContactId(), TEST_UCI);
+ Assert.assertEquals(contactElement.getName(), TEST_NAME);
+ Assert.assertEquals(contactElement.getDisplayName(), TEST_DISPLAY_NAME);
+ Assert.assertEquals(contactElement.getBtUid(), TEST_BT_UID);
+ Assert.assertEquals(contactElement.getChatState(), TEST_CHAT_STATE);
+ Assert.assertEquals(contactElement.getPresenceStatus(), TEST_STATUS_TEXT);
+ Assert.assertEquals(contactElement.getPresenceAvailability(), TEST_PRESENCE_STATE);
+ Assert.assertEquals(contactElement.getLastActivityString(), format.format(
+ TEST_LAST_ACTIVITY));
+ Assert.assertEquals(contactElement.getPriority(), TEST_PRIORITY);
+ }
+
+ @Test
+ public void handleContactListChanges_withMapEventReportVersion11() throws Exception {
+ Uri uri = mock(Uri.class);
+ mObserver.mAuthority = TEST_AUTHORITY;
+ when(uri.getAuthority()).thenReturn(TEST_AUTHORITY);
+ mObserver.mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V11;
+
+ mObserver.handleContactListChanges(uri);
+
+ verify(mProviderClient, never()).query(any(), any(), any(), any(), any(), any());
+ }
+
private BluetoothMapContentObserver.Msg createSimpleMsg() {
return new BluetoothMapContentObserver.Msg(1, 1L, 1);
}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapContentTest.java b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapContentTest.java
index d8449ec..9e1f738 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapContentTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapContentTest.java
@@ -19,6 +19,7 @@
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
@@ -47,6 +48,8 @@
import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
import com.android.bluetooth.mapapi.BluetoothMapContract;
+import com.google.android.mms.pdu.PduHeaders;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -69,8 +72,10 @@
private static final String TEST_BCC_ADDRESS = "bccName (bccAddress) <bcc@google.com>";
private static final String TEST_FROM_ADDRESS = "fromName (fromAddress) <from@google.com>";
private static final String TEST_ADDRESS = "111-1111-1111";
- private static final long TEST_DATE_SMS = 1;
+ private static final long TEST_DATE_SMS = 4;
+ private static final long TEST_DATE_MMS = 3;
private static final long TEST_DATE_EMAIL = 2;
+ private static final long TEST_DATE_IM = 1;
private static final String TEST_NAME = "test_name";
private static final String TEST_FORMATTED_NAME = "test_formatted_name";
private static final String TEST_PHONE = "test_phone";
@@ -84,6 +89,23 @@
private static final String TEST_FIRST_BT_UID = "1111";
private static final String TEST_FIRST_BT_UCI_RECIPIENT = "test_first_bt_uci_recipient";
private static final String TEST_FIRST_BT_UCI_ORIGINATOR = "test_first_bt_uci_originator";
+ private static final int TEST_NO_FILTER = 0;
+ private static final String TEST_CONTACT_NAME_FILTER = "test_contact_name_filter";
+ private static final int TEST_SIZE = 1;
+ private static final int TEST_TEXT_ONLY = 1;
+ private static final int TEST_READ_TRUE = 1;
+ private static final int TEST_READ_FALSE = 0;
+ private static final int TEST_PRIORITY_HIGH = 1;
+ private static final int TEST_SENT_YES = 2;
+ private static final int TEST_SENT_NO = 1;
+ private static final int TEST_PROTECTED = 1;
+ private static final int TEST_ATTACHMENT_TRUE = 1;
+ private static final String TEST_DELIVERY_STATE = "delivered";
+ private static final long TEST_THREAD_ID = 1;
+ private static final String TEST_ATTACHMENT_MIME_TYPE = "test_mime_type";
+ private static final String TEST_YES = "yes";
+ private static final String TEST_NO = "no";
+ private static final String TEST_RECEPTION_STATUS = "complete";
@Mock
private BluetoothMapAccountItem mAccountItem;
@@ -376,8 +398,7 @@
@Test
public void smsSelected_withNoFilter() {
- int noFilter = 0;
- when(mParams.getFilterMessageType()).thenReturn(noFilter);
+ when(mParams.getFilterMessageType()).thenReturn(TEST_NO_FILTER);
assertThat(mContent.smsSelected(mInfo, mParams)).isTrue();
}
@@ -423,8 +444,7 @@
@Test
public void mmsSelected_withNoFilter() {
- int noFilter = 0;
- when(mParams.getFilterMessageType()).thenReturn(noFilter);
+ when(mParams.getFilterMessageType()).thenReturn(TEST_NO_FILTER);
assertThat(mContent.mmsSelected(mParams)).isTrue();
}
@@ -573,7 +593,7 @@
}
@Test
- public void setSenderAddressing_withFilterMSgTypeSms_andSmsMsgTypeDraft() {
+ public void setSenderAddressing_withFilterMsgTypeSms_andSmsMsgTypeDraft() {
when(mParams.getParameterMask()).thenReturn(
(long) BluetoothMapContent.MASK_SENDER_ADDRESSING);
mInfo.mMsgType = FilterInfo.TYPE_SMS;
@@ -1044,4 +1064,421 @@
TEST_FORMATTED_NAME);
assertThat(messageMimeParsed.getRecipients().get(0).getName()).isEmpty();
}
+
+ @Test
+ public void convoListing_withNullFilterRecipient() {
+ when(mParams.getConvoParameterMask()).thenReturn(
+ (long) BluetoothMapAppParams.INVALID_VALUE_PARAMETER);
+ when(mParams.getFilterMessageType()).thenReturn(TEST_NO_FILTER);
+ when(mParams.getMaxListCount()).thenReturn(2);
+ when(mParams.getStartOffset()).thenReturn(0);
+ // This mock sets filter recipient to null
+ when(mParams.getFilterRecipient()).thenReturn(null);
+
+ MatrixCursor smsMmsCursor = new MatrixCursor(new String[] {"MmsSmsThreadColId",
+ "MmsSmsThreadColDate", "MmsSmsThreadColSnippet", "MmsSmsThreadSnippetCharset",
+ "MmsSmsThreadColRead", "MmsSmsThreadColRecipientIds"});
+ smsMmsCursor.addRow(new Object[] {TEST_ID, TEST_DATE_SMS, "test_col_snippet",
+ "test_col_snippet_cs", 1, "test_recipient_ids"});
+ smsMmsCursor.moveToFirst();
+ doReturn(smsMmsCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+ eq(BluetoothMapContent.MMS_SMS_THREAD_PROJECTION), any(), any(), any());
+
+ MatrixCursor imEmailCursor = new MatrixCursor(
+ new String[] {BluetoothMapContract.ConversationColumns.THREAD_ID,
+ BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY,
+ BluetoothMapContract.ConversationColumns.THREAD_NAME,
+ BluetoothMapContract.ConversationColumns.READ_STATUS,
+ BluetoothMapContract.ConversationColumns.VERSION_COUNTER,
+ BluetoothMapContract.ConversationColumns.SUMMARY,
+ BluetoothMapContract.ConvoContactColumns.X_BT_UID,
+ BluetoothMapContract.ConvoContactColumns.CHAT_STATE,
+ BluetoothMapContract.ConvoContactColumns.UCI,
+ BluetoothMapContract.ConvoContactColumns.NICKNAME,
+ BluetoothMapContract.ConvoContactColumns.LAST_ACTIVE,
+ BluetoothMapContract.ConvoContactColumns.NAME,
+ BluetoothMapContract.ConvoContactColumns.PRESENCE_STATE,
+ BluetoothMapContract.ConvoContactColumns.STATUS_TEXT,
+ BluetoothMapContract.ConvoContactColumns.PRIORITY});
+ imEmailCursor.addRow(new Object[] {TEST_ID, TEST_DATE_EMAIL, TEST_NAME, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0});
+ doReturn(imEmailCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+ eq(BluetoothMapContract.BT_CONVERSATION_PROJECTION), any(), any(), any());
+
+ BluetoothMapConvoListing listing = mContent.convoListing(mParams, false);
+
+ assertThat(listing.getCount()).isEqualTo(2);
+ BluetoothMapConvoListingElement emailElement = listing.getList().get(1);
+ assertThat(emailElement.getType()).isEqualTo(TYPE.EMAIL);
+ assertThat(emailElement.getLastActivity()).isEqualTo(TEST_DATE_EMAIL);
+ assertThat(emailElement.getName()).isEqualTo(TEST_NAME);
+ assertThat(emailElement.getReadBool()).isFalse();
+ BluetoothMapConvoListingElement smsElement = listing.getList().get(0);
+ assertThat(smsElement.getType()).isEqualTo(TYPE.SMS_GSM);
+ assertThat(smsElement.getLastActivity()).isEqualTo(TEST_DATE_SMS);
+ assertThat(smsElement.getName()).isEqualTo("");
+ assertThat(smsElement.getReadBool()).isTrue();
+ }
+
+ @Test
+ public void convoListing_withNonNullFilterRecipient() {
+ when(mParams.getConvoParameterMask()).thenReturn(
+ (long) BluetoothMapAppParams.INVALID_VALUE_PARAMETER);
+ when(mParams.getFilterMessageType()).thenReturn(BluetoothMapAppParams.FILTER_NO_EMAIL);
+ when(mParams.getMaxListCount()).thenReturn(2);
+ when(mParams.getStartOffset()).thenReturn(0);
+ // This mock sets filter recipient to non null
+ when(mParams.getFilterRecipient()).thenReturn(TEST_CONTACT_NAME_FILTER);
+
+ MatrixCursor smsMmsCursor = new MatrixCursor(new String[] {"MmsSmsThreadColId",
+ "MmsSmsThreadColDate", "MmsSmsThreadColSnippet", "MmsSmsThreadSnippetCharset",
+ "MmsSmsThreadColRead", "MmsSmsThreadColRecipientIds"});
+ smsMmsCursor.addRow(new Object[] {TEST_ID, TEST_DATE_SMS, "test_col_snippet",
+ "test_col_snippet_cs", 1, String.valueOf(TEST_ID)});
+ smsMmsCursor.moveToFirst();
+ doReturn(smsMmsCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+ eq(BluetoothMapContent.MMS_SMS_THREAD_PROJECTION), any(), any(), any());
+
+ MatrixCursor addressCursor = new MatrixCursor(new String[] {"COL_ADDR_ID",
+ "COL_ADDR_ADDR"});
+ addressCursor.addRow(new Object[]{TEST_ID, TEST_ADDRESS});
+ doReturn(addressCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+ eq(SmsMmsContacts.ADDRESS_PROJECTION), any(), any(), any());
+
+ MatrixCursor contactCursor = new MatrixCursor(new String[] {"COL_CONTACT_ID",
+ "COL_CONTACT_NAME"});
+ contactCursor.addRow(new Object[]{TEST_ID, TEST_NAME});
+ doReturn(contactCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+ eq(SmsMmsContacts.CONTACT_PROJECTION), any(), any(), any());
+
+ MatrixCursor imEmailCursor = new MatrixCursor(
+ new String[] {BluetoothMapContract.ConversationColumns.THREAD_ID,
+ BluetoothMapContract.ConversationColumns.LAST_THREAD_ACTIVITY,
+ BluetoothMapContract.ConversationColumns.THREAD_NAME,
+ BluetoothMapContract.ConversationColumns.READ_STATUS,
+ BluetoothMapContract.ConversationColumns.VERSION_COUNTER,
+ BluetoothMapContract.ConversationColumns.SUMMARY,
+ BluetoothMapContract.ConvoContactColumns.X_BT_UID,
+ BluetoothMapContract.ConvoContactColumns.CHAT_STATE,
+ BluetoothMapContract.ConvoContactColumns.UCI,
+ BluetoothMapContract.ConvoContactColumns.NICKNAME,
+ BluetoothMapContract.ConvoContactColumns.LAST_ACTIVE,
+ BluetoothMapContract.ConvoContactColumns.NAME,
+ BluetoothMapContract.ConvoContactColumns.PRESENCE_STATE,
+ BluetoothMapContract.ConvoContactColumns.STATUS_TEXT,
+ BluetoothMapContract.ConvoContactColumns.PRIORITY});
+ imEmailCursor.addRow(new Object[] {TEST_ID, TEST_DATE_EMAIL, TEST_NAME, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0});
+ doReturn(imEmailCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+ eq(BluetoothMapContract.BT_CONVERSATION_PROJECTION), any(), any(), any());
+
+ BluetoothMapConvoListing listing = mContent.convoListing(mParams, false);
+
+ assertThat(listing.getCount()).isEqualTo(2);
+ BluetoothMapConvoListingElement imElement = listing.getList().get(1);
+ assertThat(imElement.getType()).isEqualTo(TYPE.IM);
+ assertThat(imElement.getLastActivity()).isEqualTo(TEST_DATE_EMAIL);
+ assertThat(imElement.getName()).isEqualTo(TEST_NAME);
+ assertThat(imElement.getReadBool()).isFalse();
+ BluetoothMapConvoListingElement smsElement = listing.getList().get(0);
+ assertThat(smsElement.getType()).isEqualTo(TYPE.SMS_GSM);
+ assertThat(smsElement.getLastActivity()).isEqualTo(TEST_DATE_SMS);
+ assertThat(smsElement.getName()).isEqualTo("");
+ assertThat(smsElement.getReadBool()).isTrue();
+ }
+
+ @Test
+ public void msgListing_withSmsCursorOnly() {
+ when(mParams.getParameterMask()).thenReturn(
+ (long) BluetoothMapAppParams.INVALID_VALUE_PARAMETER);
+ int noMms = BluetoothMapAppParams.FILTER_NO_MMS;
+ when(mParams.getFilterMessageType()).thenReturn(noMms);
+ when(mParams.getMaxListCount()).thenReturn(1);
+ when(mParams.getStartOffset()).thenReturn(0);
+
+ mCurrentFolder.setHasSmsMmsContent(true);
+ mCurrentFolder.setFolderId(TEST_ID);
+ mContent.mMsgListingVersion = BluetoothMapUtils.MAP_MESSAGE_LISTING_FORMAT_V11;
+
+ MatrixCursor smsCursor = new MatrixCursor(new String[] {BaseColumns._ID, Telephony.Sms.TYPE,
+ Telephony.Sms.READ, Telephony.Sms.BODY, Telephony.Sms.ADDRESS, Telephony.Sms.DATE,
+ Telephony.Sms.THREAD_ID, ContactsContract.Contacts.DISPLAY_NAME});
+ smsCursor.addRow(new Object[] {TEST_ID, TEST_SENT_NO, TEST_READ_TRUE, TEST_SUBJECT,
+ TEST_ADDRESS, TEST_DATE_SMS, TEST_THREAD_ID, TEST_PHONE_NAME});
+ doReturn(smsCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+ eq(BluetoothMapContent.SMS_PROJECTION), any(), any(), any());
+ doReturn(smsCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+ eq(new String[] {ContactsContract.Contacts._ID,
+ ContactsContract.Contacts.DISPLAY_NAME}), any(), any(), any());
+
+ BluetoothMapMessageListing listing = mContent.msgListing(mCurrentFolder, mParams);
+ assertThat(listing.getCount()).isEqualTo(1);
+
+ BluetoothMapMessageListingElement smsElement = listing.getList().get(0);
+ assertThat(smsElement.getHandle()).isEqualTo(TEST_ID);
+ assertThat(smsElement.getDateTime()).isEqualTo(TEST_DATE_SMS);
+ assertThat(smsElement.getType()).isEqualTo(TYPE.SMS_GSM);
+ assertThat(smsElement.getReadBool()).isTrue();
+ assertThat(smsElement.getSenderAddressing()).isEqualTo(
+ PhoneNumberUtils.extractNetworkPortion(TEST_ADDRESS));
+ assertThat(smsElement.getSenderName()).isEqualTo(TEST_PHONE_NAME);
+ assertThat(smsElement.getSize()).isEqualTo(TEST_SUBJECT.length());
+ assertThat(smsElement.getPriority()).isEqualTo(TEST_NO);
+ assertThat(smsElement.getSent()).isEqualTo(TEST_NO);
+ assertThat(smsElement.getProtect()).isEqualTo(TEST_NO);
+ assertThat(smsElement.getReceptionStatus()).isEqualTo(TEST_RECEPTION_STATUS);
+ assertThat(smsElement.getAttachmentSize()).isEqualTo(0);
+ assertThat(smsElement.getDeliveryStatus()).isEqualTo(TEST_DELIVERY_STATE);
+ }
+
+ @Test
+ public void msgListing_withMmsCursorOnly() {
+ when(mParams.getParameterMask()).thenReturn(
+ (long) BluetoothMapAppParams.INVALID_VALUE_PARAMETER);
+ int onlyMms =
+ BluetoothMapAppParams.FILTER_NO_EMAIL | BluetoothMapAppParams.FILTER_NO_SMS_CDMA
+ | BluetoothMapAppParams.FILTER_NO_SMS_GSM
+ | BluetoothMapAppParams.FILTER_NO_IM;
+ when(mParams.getFilterMessageType()).thenReturn(onlyMms);
+ when(mParams.getMaxListCount()).thenReturn(1);
+ when(mParams.getStartOffset()).thenReturn(0);
+
+ mCurrentFolder.setHasSmsMmsContent(true);
+ mCurrentFolder.setFolderId(TEST_ID);
+ mContent.mMsgListingVersion = BluetoothMapUtils.MAP_MESSAGE_LISTING_FORMAT_V11;
+
+ MatrixCursor mmsCursor = new MatrixCursor(new String[] {BaseColumns._ID,
+ Telephony.Mms.MESSAGE_BOX, Telephony.Mms.READ, Telephony.Mms.MESSAGE_SIZE,
+ Telephony.Mms.TEXT_ONLY, Telephony.Mms.DATE, Telephony.Mms.SUBJECT,
+ Telephony.Mms.THREAD_ID, Telephony.Mms.Addr.ADDRESS,
+ ContactsContract.Contacts.DISPLAY_NAME, Telephony.Mms.PRIORITY});
+ mmsCursor.addRow(new Object[] {TEST_ID, TEST_SENT_NO, TEST_READ_FALSE, TEST_SIZE,
+ TEST_TEXT_ONLY, TEST_DATE_MMS, TEST_SUBJECT, TEST_THREAD_ID, TEST_PHONE,
+ TEST_PHONE_NAME, PduHeaders.PRIORITY_HIGH});
+ doReturn(mmsCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+ eq(BluetoothMapContent.MMS_PROJECTION), any(), any(), any());
+ doReturn(mmsCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+ eq(new String[] {Telephony.Mms.Addr.ADDRESS}), any(), any(), any());
+ doReturn(mmsCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+ eq(new String[] {ContactsContract.Contacts._ID,
+ ContactsContract.Contacts.DISPLAY_NAME}), any(), any(), any());
+
+ BluetoothMapMessageListing listing = mContent.msgListing(mCurrentFolder, mParams);
+ assertThat(listing.getCount()).isEqualTo(1);
+
+ BluetoothMapMessageListingElement mmsElement = listing.getList().get(0);
+ assertThat(mmsElement.getHandle()).isEqualTo(TEST_ID);
+ assertThat(mmsElement.getDateTime()).isEqualTo(TEST_DATE_MMS * 1000L);
+ assertThat(mmsElement.getType()).isEqualTo(TYPE.MMS);
+ assertThat(mmsElement.getReadBool()).isFalse();
+ assertThat(mmsElement.getSenderAddressing()).isEqualTo(TEST_PHONE);
+ assertThat(mmsElement.getSenderName()).isEqualTo(TEST_PHONE_NAME);
+ assertThat(mmsElement.getSize()).isEqualTo(TEST_SIZE);
+ assertThat(mmsElement.getPriority()).isEqualTo(TEST_YES);
+ assertThat(mmsElement.getSent()).isEqualTo(TEST_NO);
+ assertThat(mmsElement.getProtect()).isEqualTo(TEST_NO);
+ assertThat(mmsElement.getReceptionStatus()).isEqualTo(TEST_RECEPTION_STATUS);
+ assertThat(mmsElement.getAttachmentSize()).isEqualTo(0);
+ assertThat(mmsElement.getDeliveryStatus()).isEqualTo(TEST_DELIVERY_STATE);
+ }
+
+ @Test
+ public void msgListing_withEmailCursorOnly() {
+ when(mParams.getParameterMask()).thenReturn(
+ (long) BluetoothMapAppParams.INVALID_VALUE_PARAMETER);
+ int onlyEmail =
+ BluetoothMapAppParams.FILTER_NO_MMS | BluetoothMapAppParams.FILTER_NO_SMS_CDMA
+ | BluetoothMapAppParams.FILTER_NO_SMS_GSM
+ | BluetoothMapAppParams.FILTER_NO_IM;
+ when(mParams.getFilterMessageType()).thenReturn(onlyEmail);
+ when(mParams.getMaxListCount()).thenReturn(1);
+ when(mParams.getStartOffset()).thenReturn(0);
+
+ mCurrentFolder.setHasEmailContent(true);
+ mCurrentFolder.setFolderId(TEST_ID);
+ mContent.mMsgListingVersion = BluetoothMapUtils.MAP_MESSAGE_LISTING_FORMAT_V11;
+
+ MatrixCursor emailCursor = new MatrixCursor(new String[] {
+ BluetoothMapContract.MessageColumns._ID,
+ BluetoothMapContract.MessageColumns.DATE,
+ BluetoothMapContract.MessageColumns.SUBJECT,
+ BluetoothMapContract.MessageColumns.FOLDER_ID,
+ BluetoothMapContract.MessageColumns.FLAG_READ,
+ BluetoothMapContract.MessageColumns.MESSAGE_SIZE,
+ BluetoothMapContract.MessageColumns.FROM_LIST,
+ BluetoothMapContract.MessageColumns.TO_LIST,
+ BluetoothMapContract.MessageColumns.FLAG_ATTACHMENT,
+ BluetoothMapContract.MessageColumns.ATTACHMENT_SIZE,
+ BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY,
+ BluetoothMapContract.MessageColumns.FLAG_PROTECTED,
+ BluetoothMapContract.MessageColumns.RECEPTION_STATE,
+ BluetoothMapContract.MessageColumns.DEVILERY_STATE,
+ BluetoothMapContract.MessageColumns.THREAD_ID,
+ BluetoothMapContract.MessageColumns.CC_LIST,
+ BluetoothMapContract.MessageColumns.BCC_LIST,
+ BluetoothMapContract.MessageColumns.REPLY_TO_LIST});
+ emailCursor.addRow(new Object[] {TEST_ID, TEST_DATE_EMAIL, TEST_SUBJECT, TEST_SENT_YES,
+ TEST_READ_TRUE, TEST_SIZE, TEST_FROM_ADDRESS, TEST_TO_ADDRESS, TEST_ATTACHMENT_TRUE,
+ 0, TEST_PRIORITY_HIGH, TEST_PROTECTED, 0, TEST_DELIVERY_STATE,
+ TEST_THREAD_ID, TEST_CC_ADDRESS, TEST_BCC_ADDRESS, TEST_TO_ADDRESS});
+ doReturn(emailCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+ eq(BluetoothMapContract.BT_MESSAGE_PROJECTION), any(), any(), any());
+
+ BluetoothMapMessageListing listing = mContent.msgListing(mCurrentFolder, mParams);
+ assertThat(listing.getCount()).isEqualTo(1);
+
+ BluetoothMapMessageListingElement emailElement = listing.getList().get(0);
+ assertThat(emailElement.getHandle()).isEqualTo(TEST_ID);
+ assertThat(emailElement.getDateTime()).isEqualTo(TEST_DATE_EMAIL);
+ assertThat(emailElement.getType()).isEqualTo(TYPE.EMAIL);
+ assertThat(emailElement.getReadBool()).isTrue();
+ StringBuilder expectedAddress = new StringBuilder();
+ expectedAddress.append(Rfc822Tokenizer.tokenize(TEST_FROM_ADDRESS)[0].getAddress());
+ assertThat(emailElement.getSenderAddressing()).isEqualTo(expectedAddress.toString());
+ StringBuilder expectedName = new StringBuilder();
+ expectedName.append(Rfc822Tokenizer.tokenize(TEST_FROM_ADDRESS)[0].getName());
+ assertThat(emailElement.getSenderName()).isEqualTo(expectedName.toString());
+ assertThat(emailElement.getSize()).isEqualTo(TEST_SIZE);
+ assertThat(emailElement.getPriority()).isEqualTo(TEST_YES);
+ assertThat(emailElement.getSent()).isEqualTo(TEST_YES);
+ assertThat(emailElement.getProtect()).isEqualTo(TEST_YES);
+ assertThat(emailElement.getReceptionStatus()).isEqualTo(TEST_RECEPTION_STATUS);
+ assertThat(emailElement.getAttachmentSize()).isEqualTo(TEST_SIZE);
+ assertThat(emailElement.getDeliveryStatus()).isEqualTo(TEST_DELIVERY_STATE);
+ }
+
+ @Test
+ public void msgListing_withImCursorOnly() {
+ when(mParams.getParameterMask()).thenReturn(
+ (long) BluetoothMapAppParams.INVALID_VALUE_PARAMETER);
+ int onlyIm = BluetoothMapAppParams.FILTER_NO_MMS | BluetoothMapAppParams.FILTER_NO_SMS_CDMA
+ | BluetoothMapAppParams.FILTER_NO_SMS_GSM | BluetoothMapAppParams.FILTER_NO_EMAIL;
+ when(mParams.getFilterMessageType()).thenReturn(onlyIm);
+ when(mParams.getMaxListCount()).thenReturn(1);
+ when(mParams.getStartOffset()).thenReturn(0);
+
+ mCurrentFolder.setHasImContent(true);
+ mCurrentFolder.setFolderId(TEST_ID);
+ mContent.mMsgListingVersion = BluetoothMapUtils.MAP_MESSAGE_LISTING_FORMAT_V11;
+
+ MatrixCursor imCursor = new MatrixCursor(new String[] {
+ BluetoothMapContract.MessageColumns._ID,
+ BluetoothMapContract.MessageColumns.DATE,
+ BluetoothMapContract.MessageColumns.SUBJECT,
+ BluetoothMapContract.MessageColumns.FOLDER_ID,
+ BluetoothMapContract.MessageColumns.FLAG_READ,
+ BluetoothMapContract.MessageColumns.MESSAGE_SIZE,
+ BluetoothMapContract.MessageColumns.FROM_LIST,
+ BluetoothMapContract.MessageColumns.TO_LIST,
+ BluetoothMapContract.MessageColumns.FLAG_ATTACHMENT,
+ BluetoothMapContract.MessageColumns.ATTACHMENT_SIZE,
+ BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY,
+ BluetoothMapContract.MessageColumns.FLAG_PROTECTED,
+ BluetoothMapContract.MessageColumns.RECEPTION_STATE,
+ BluetoothMapContract.MessageColumns.DEVILERY_STATE,
+ BluetoothMapContract.MessageColumns.THREAD_ID,
+ BluetoothMapContract.MessageColumns.THREAD_NAME,
+ BluetoothMapContract.MessageColumns.ATTACHMENT_MINE_TYPES,
+ BluetoothMapContract.MessageColumns.BODY,
+ BluetoothMapContract.ConvoContactColumns.UCI,
+ BluetoothMapContract.ConvoContactColumns.NAME});
+ imCursor.addRow(new Object[] {TEST_ID, TEST_DATE_IM, TEST_SUBJECT, TEST_SENT_NO,
+ TEST_READ_FALSE, TEST_SIZE, TEST_ID, TEST_TO_ADDRESS, TEST_ATTACHMENT_TRUE,
+ 0 /*=attachment size*/, TEST_PRIORITY_HIGH, TEST_PROTECTED, 0, TEST_DELIVERY_STATE,
+ TEST_THREAD_ID, TEST_NAME, TEST_ATTACHMENT_MIME_TYPE, 0, TEST_ADDRESS, TEST_NAME});
+ doReturn(imCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+ eq(BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION), any(), any(), any());
+ doReturn(imCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+ eq(BluetoothMapContract.BT_CONTACT_PROJECTION), any(), any(), any());
+
+ BluetoothMapMessageListing listing = mContent.msgListing(mCurrentFolder, mParams);
+ assertThat(listing.getCount()).isEqualTo(1);
+
+ BluetoothMapMessageListingElement imElement = listing.getList().get(0);
+ assertThat(imElement.getHandle()).isEqualTo(TEST_ID);
+ assertThat(imElement.getDateTime()).isEqualTo(TEST_DATE_IM);
+ assertThat(imElement.getType()).isEqualTo(TYPE.IM);
+ assertThat(imElement.getReadBool()).isFalse();
+ assertThat(imElement.getSenderAddressing()).isEqualTo(TEST_ADDRESS);
+ assertThat(imElement.getSenderName()).isEqualTo(TEST_NAME);
+ assertThat(imElement.getSize()).isEqualTo(TEST_SIZE);
+ assertThat(imElement.getPriority()).isEqualTo(TEST_YES);
+ assertThat(imElement.getSent()).isEqualTo(TEST_NO);
+ assertThat(imElement.getProtect()).isEqualTo(TEST_YES);
+ assertThat(imElement.getReceptionStatus()).isEqualTo(TEST_RECEPTION_STATUS);
+ assertThat(imElement.getAttachmentSize()).isEqualTo(TEST_SIZE);
+ assertThat(imElement.getAttachmentMimeTypes()).isEqualTo(TEST_ATTACHMENT_MIME_TYPE);
+ assertThat(imElement.getDeliveryStatus()).isEqualTo(TEST_DELIVERY_STATE);
+ assertThat(imElement.getThreadName()).isEqualTo(TEST_NAME);
+ }
+
+ @Test
+ public void msgListingSize() {
+ when(mParams.getFilterMessageType()).thenReturn(TEST_NO_FILTER);
+ mCurrentFolder.setHasSmsMmsContent(true);
+ mCurrentFolder.setHasEmailContent(true);
+ mCurrentFolder.setHasImContent(true);
+ mCurrentFolder.setFolderId(TEST_ID);
+
+ MatrixCursor smsCursor = new MatrixCursor(new String[] {"Placeholder"});
+ // Making cursor.getCount() as 1
+ smsCursor.addRow(new Object[] {1});
+ doReturn(smsCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+ eq(BluetoothMapContent.SMS_PROJECTION), any(), any(), any());
+
+ MatrixCursor mmsCursor = new MatrixCursor(new String[] {"Placeholder"});
+ // Making cursor.getCount() as 1
+ mmsCursor.addRow(new Object[] {1});
+ doReturn(mmsCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+ eq(BluetoothMapContent.MMS_PROJECTION), any(), any(), any());
+
+ MatrixCursor emailCursor = new MatrixCursor(new String[] {"Placeholder"});
+ // Making cursor.getCount() as 1
+ emailCursor.addRow(new Object[] {1});
+ doReturn(emailCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+ eq(BluetoothMapContract.BT_MESSAGE_PROJECTION), any(), any(), any());
+
+ MatrixCursor imCursor = new MatrixCursor(new String[] {"Placeholder"});
+ // Making cursor.getCount() as 1
+ imCursor.addRow(new Object[] {1});
+ doReturn(imCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+ eq(BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION), any(), any(), any());
+
+ assertThat(mContent.msgListingSize(mCurrentFolder, mParams)).isEqualTo(4);
+ }
+
+ @Test
+ public void msgListingHasUnread() {
+ when(mParams.getFilterMessageType()).thenReturn(TEST_NO_FILTER);
+ mCurrentFolder.setHasSmsMmsContent(true);
+ mCurrentFolder.setHasEmailContent(true);
+ mCurrentFolder.setHasImContent(true);
+ mCurrentFolder.setFolderId(TEST_ID);
+
+ MatrixCursor smsCursor = new MatrixCursor(new String[] {"Placeholder"});
+ // Making cursor.getCount() as 1
+ smsCursor.addRow(new Object[] {1});
+ doReturn(smsCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+ eq(BluetoothMapContent.SMS_PROJECTION), any(), any(), any());
+
+ MatrixCursor mmsCursor = new MatrixCursor(new String[] {"Placeholder"});
+ // Making cursor.getCount() as 1
+ mmsCursor.addRow(new Object[] {1});
+ doReturn(mmsCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+ eq(BluetoothMapContent.MMS_PROJECTION), any(), any(), any());
+
+ MatrixCursor emailCursor = new MatrixCursor(new String[] {"Placeholder"});
+ // Making cursor.getCount() as 1
+ emailCursor.addRow(new Object[] {1});
+ doReturn(emailCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+ eq(BluetoothMapContract.BT_MESSAGE_PROJECTION), any(), any(), any());
+
+ MatrixCursor imCursor = new MatrixCursor(new String[] {"Placeholder"});
+ // Making cursor.getCount() as 1
+ imCursor.addRow(new Object[] {1});
+ doReturn(imCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+ eq(BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION), any(), any(), any());
+
+ assertThat(mContent.msgListingHasUnread(mCurrentFolder, mParams)).isTrue();
+ }
}
\ No newline at end of file
diff --git a/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapObexServerTest.java b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapObexServerTest.java
index 0a6f6bd..4b252fc 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapObexServerTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapObexServerTest.java
@@ -18,6 +18,7 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
@@ -39,6 +40,7 @@
import com.android.bluetooth.BluetoothMethodProxy;
import com.android.bluetooth.mapapi.BluetoothMapContract;
import com.android.obex.ResponseCodes;
+import com.android.obex.Operation;
import org.junit.Before;
import org.junit.Test;
@@ -185,6 +187,53 @@
assertThat(parentFolder.getFolderById(childId)).isNotNull();
}
+ @Test
+ public void setMsgTypeFilterParams_withAccountNull_andOverwriteTrue() throws Exception {
+ BluetoothMapObexServer obexServer = new BluetoothMapObexServer(null, mContext, mObserver,
+ mMasInstance, null, false);
+
+ obexServer.setMsgTypeFilterParams(mParams, true);
+
+ int expectedMask = 0;
+ expectedMask |= BluetoothMapAppParams.FILTER_NO_SMS_CDMA;
+ expectedMask |= BluetoothMapAppParams.FILTER_NO_SMS_GSM;
+ expectedMask |= BluetoothMapAppParams.FILTER_NO_MMS;
+ expectedMask |= BluetoothMapAppParams.FILTER_NO_EMAIL;
+ expectedMask |= BluetoothMapAppParams.FILTER_NO_IM;
+ assertThat(mParams.getFilterMessageType()).isEqualTo(expectedMask);
+ }
+
+ @Test
+ public void setMsgTypeFilterParams_withInvalidFilterMessageType() throws Exception {
+ BluetoothMapAccountItem accountItemWithTypeEmail = BluetoothMapAccountItem.create(TEST_ID,
+ TEST_NAME, TEST_PACKAGE_NAME, TEST_PROVIDER_AUTHORITY, TEST_DRAWABLE,
+ BluetoothMapUtils.TYPE.EMAIL, TEST_UCI, TEST_UCI_PREFIX);
+ BluetoothMapObexServer obexServer = new BluetoothMapObexServer(null, mContext, mObserver,
+ mMasInstance, accountItemWithTypeEmail, TEST_ENABLE_SMS_MMS);
+
+ // Passing mParams without any previous settings pass invalid filter message type
+ assertThrows(IllegalArgumentException.class,
+ () -> obexServer.setMsgTypeFilterParams(mParams, false));
+ }
+
+ @Test
+ public void setMsgTypeFilterParams_withValidFilterMessageType() throws Exception {
+ BluetoothMapAccountItem accountItemWithTypeIm = BluetoothMapAccountItem.create(TEST_ID,
+ TEST_NAME, TEST_PACKAGE_NAME, TEST_PROVIDER_AUTHORITY, TEST_DRAWABLE,
+ BluetoothMapUtils.TYPE.IM, TEST_UCI, TEST_UCI_PREFIX);
+ BluetoothMapObexServer obexServer = new BluetoothMapObexServer(null, mContext, mObserver,
+ mMasInstance, accountItemWithTypeIm, TEST_ENABLE_SMS_MMS);
+ int expectedMask = 1;
+ mParams.setFilterMessageType(expectedMask);
+
+ obexServer.setMsgTypeFilterParams(mParams, false);
+
+ int masFilterMask = 0;
+ masFilterMask |= BluetoothMapAppParams.FILTER_NO_EMAIL;
+ expectedMask |= masFilterMask;
+ assertThat(mParams.getFilterMessageType()).isEqualTo(expectedMask);
+ }
+
private void setUpBluetoothMapAppParams(BluetoothMapAppParams params) {
params.setPresenceAvailability(1);
params.setPresenceStatus("test_presence_status");
diff --git a/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapSettingsTest.java b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapSettingsTest.java
new file mode 100644
index 0000000..df751e3
--- /dev/null
+++ b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapSettingsTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.map;
+
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+import static android.content.pm.PackageManager.DONT_KILL_APP;
+
+import static androidx.test.espresso.Espresso.onView;
+import static androidx.test.espresso.assertion.ViewAssertions.matches;
+import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static androidx.test.espresso.matcher.ViewMatchers.withId;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.filters.LargeTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.bluetooth.R;
+
+import org.junit.After;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class BluetoothMapSettingsTest {
+
+ Context mTargetContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ Intent mIntent;
+
+ ActivityScenario<BluetoothMapSettings> mActivityScenario;
+
+ @Before
+ public void setUp() {
+ Assume.assumeTrue("Ignore test when BluetoothMapService is not enabled",
+ BluetoothMapService.isEnabled());
+ enableActivity(true);
+ mIntent = new Intent();
+ mIntent.setClass(mTargetContext, BluetoothMapSettings.class);
+ mActivityScenario = ActivityScenario.launch(mIntent);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (mActivityScenario != null) {
+ // Workaround for b/159805732. Without this, test hangs for 45 seconds.
+ Thread.sleep(1_000);
+ mActivityScenario.close();
+ }
+ enableActivity(false);
+ }
+
+ @Test
+ public void initialize() throws Exception {
+ onView(withId(R.id.bluetooth_map_settings_list_view)).check(matches(isDisplayed()));
+ }
+
+ private void enableActivity(boolean enable) {
+ int enabledState = enable ? COMPONENT_ENABLED_STATE_ENABLED
+ : COMPONENT_ENABLED_STATE_DEFAULT;
+
+ mTargetContext.getPackageManager().setApplicationEnabledSetting(
+ mTargetContext.getPackageName(), enabledState, DONT_KILL_APP);
+
+ ComponentName activityName = new ComponentName(mTargetContext, BluetoothMapSettings.class);
+ mTargetContext.getPackageManager().setComponentEnabledSetting(
+ activityName, enabledState, DONT_KILL_APP);
+ }
+}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/mapclient/MapClientContentTest.java b/android/app/tests/unit/src/com/android/bluetooth/mapclient/MapClientContentTest.java
index 8b3eced..29b59f3 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/mapclient/MapClientContentTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/mapclient/MapClientContentTest.java
@@ -364,6 +364,17 @@
eq(SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM));
}
+ /**
+ * Test to validate that cleaning content does not crash when no subscription are available.
+ */
+ @Test
+ public void testCleanUpWithNoSubscriptions() {
+ when(mMockSubscriptionManager.getActiveSubscriptionInfoList())
+ .thenReturn(null);
+
+ MapClientContent.clearAllContent(mMockContext);
+ }
+
void createTestMessages() {
mOriginator = new VCardEntry();
VCardProperty property = new VCardProperty();
diff --git a/android/app/tests/unit/src/com/android/bluetooth/mapclient/MapClientServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/mapclient/MapClientServiceTest.java
index 07e1266..93365e5 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/mapclient/MapClientServiceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/mapclient/MapClientServiceTest.java
@@ -301,7 +301,7 @@
MceStateMachine sm = mock(MceStateMachine.class);
mService.getInstanceMap().put(mRemoteDevice, sm);
- Intent intent = new Intent(BluetoothHeadsetClient.ACTION_CONNECTION_STATE_CHANGED);
+ Intent intent = new Intent(BluetoothDevice.ACTION_SDP_RECORD);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
intent.putExtra(BluetoothProfile.EXTRA_STATE, BluetoothProfile.STATE_DISCONNECTED);
intent.putExtra(BluetoothDevice.EXTRA_UUID, BluetoothUuid.MAS);
diff --git a/android/app/tests/unit/src/com/android/bluetooth/mapclient/MapClientTest.java b/android/app/tests/unit/src/com/android/bluetooth/mapclient/MapClientTest.java
index c78616e..7c80bff 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/mapclient/MapClientTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/mapclient/MapClientTest.java
@@ -122,6 +122,7 @@
// is the statemachine created
Map<BluetoothDevice, MceStateMachine> map = mService.getInstanceMap();
+
Assert.assertEquals(1, map.size());
Assert.assertNotNull(map.get(device));
TestUtils.waitForLooperToFinishScheduledTask(mService.getMainLooper());
diff --git a/android/app/tests/unit/src/com/android/bluetooth/mapclient/MessagesFilterTest.java b/android/app/tests/unit/src/com/android/bluetooth/mapclient/MessagesFilterTest.java
new file mode 100644
index 0000000..9c6a307
--- /dev/null
+++ b/android/app/tests/unit/src/com/android/bluetooth/mapclient/MessagesFilterTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.mapclient;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class MessagesFilterTest {
+
+ @Test
+ public void setOriginator() {
+ MessagesFilter filter = new MessagesFilter();
+
+ String originator = "test_originator";
+ filter.setOriginator(originator);
+ assertThat(filter.originator).isEqualTo(originator);
+
+ filter.setOriginator("");
+ assertThat(filter.originator).isEqualTo(null); // Empty string is stored as null
+
+ filter.setOriginator(null);
+ assertThat(filter.originator).isEqualTo(null);
+ }
+
+ @Test
+ public void setPriority() {
+ MessagesFilter filter = new MessagesFilter();
+
+ byte priority = 5;
+ filter.setPriority(priority);
+
+ assertThat(filter.priority).isEqualTo(priority);
+ }
+
+ @Test
+ public void setReadStatus() {
+ MessagesFilter filter = new MessagesFilter();
+
+ byte readStatus = 5;
+ filter.setReadStatus(readStatus);
+
+ assertThat(filter.readStatus).isEqualTo(readStatus);
+ }
+
+ @Test
+ public void setRecipient() {
+ MessagesFilter filter = new MessagesFilter();
+
+ String recipient = "test_originator";
+ filter.setRecipient(recipient);
+ assertThat(filter.recipient).isEqualTo(recipient);
+
+ filter.setRecipient("");
+ assertThat(filter.recipient).isEqualTo(null); // Empty string is stored as null
+
+ filter.setRecipient(null);
+ assertThat(filter.recipient).isEqualTo(null);
+ }
+
+}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/mapclient/MessagesListingTest.java b/android/app/tests/unit/src/com/android/bluetooth/mapclient/MessagesListingTest.java
new file mode 100644
index 0000000..48ece84
--- /dev/null
+++ b/android/app/tests/unit/src/com/android/bluetooth/mapclient/MessagesListingTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.mapclient;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class MessagesListingTest {
+
+ @Test
+ public void constructor() {
+ String handle = "FFAB";
+ String subject = "test_subject";
+ final StringBuilder xml = new StringBuilder();
+ xml.append("<msg\n");
+ xml.append("handle=\"" + handle + "\"\n");
+ xml.append("subject=\"" + subject + "\"\n");
+ xml.append("/>\n");
+ ByteArrayInputStream stream = new ByteArrayInputStream(xml.toString().getBytes());
+
+ MessagesListing listing = new MessagesListing(stream);
+
+ assertThat(listing.getList()).hasSize(1);
+ Message msg = listing.getList().get(0);
+ assertThat(msg.getHandle()).isEqualTo(handle);
+ assertThat(msg.getSubject()).isEqualTo(subject);
+ }
+}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/mapclient/MnsObexServerTest.java b/android/app/tests/unit/src/com/android/bluetooth/mapclient/MnsObexServerTest.java
new file mode 100644
index 0000000..e57ab50
--- /dev/null
+++ b/android/app/tests/unit/src/com/android/bluetooth/mapclient/MnsObexServerTest.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.mapclient;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.os.Handler;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.obex.HeaderSet;
+import com.android.obex.Operation;
+import com.android.obex.ResponseCodes;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class MnsObexServerTest {
+
+ @Mock
+ MceStateMachine mStateMachine;
+
+ MnsObexServer mServer;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mServer = new MnsObexServer(mStateMachine, null);
+ }
+
+ @Test
+ public void onConnect_whenUuidIsWrong() {
+ byte[] wrongUuid = new byte[]{};
+ HeaderSet request = new HeaderSet();
+ request.setHeader(HeaderSet.TARGET, wrongUuid);
+ HeaderSet reply = new HeaderSet();
+
+ assertThat(mServer.onConnect(request, reply))
+ .isEqualTo(ResponseCodes.OBEX_HTTP_NOT_ACCEPTABLE);
+ }
+
+ @Test
+ public void onConnect_withCorrectUuid() throws Exception {
+ HeaderSet request = new HeaderSet();
+ request.setHeader(HeaderSet.TARGET, MnsObexServer.MNS_TARGET);
+ HeaderSet reply = new HeaderSet();
+
+ assertThat(mServer.onConnect(request, reply)).isEqualTo(ResponseCodes.OBEX_HTTP_OK);
+ assertThat(reply.getHeader(HeaderSet.WHO)).isEqualTo(MnsObexServer.MNS_TARGET);
+ }
+
+ @Test
+ public void onDisconnect_callsStateMachineDisconnect() {
+ HeaderSet request = new HeaderSet();
+ HeaderSet reply = new HeaderSet();
+
+ mServer.onDisconnect(request, reply);
+
+ verify(mStateMachine).disconnect();
+ }
+
+ @Test
+ public void onGet_returnsBadRequest() {
+ Operation op = mock(Operation.class);
+
+ assertThat(mServer.onGet(op)).isEqualTo(ResponseCodes.OBEX_HTTP_BAD_REQUEST);
+ }
+
+ @Test
+ public void onPut_whenTypeIsInvalid_returnsBadRequest() throws Exception {
+ HeaderSet headerSet = new HeaderSet();
+ headerSet.setHeader(HeaderSet.TYPE, "some_invalid_type");
+ Operation op = mock(Operation.class);
+ when(op.getReceivedHeader()).thenReturn(headerSet);
+
+ assertThat(mServer.onPut(op)).isEqualTo(ResponseCodes.OBEX_HTTP_BAD_REQUEST);
+ }
+
+ @Test
+ public void onPut_whenHeaderSetIsValid_returnsOk() throws Exception {
+ final StringBuilder xml = new StringBuilder();
+ xml.append("<event\n");
+ xml.append(" type=\"test_type\"\n");
+ xml.append(" handle=\"FFAB\"\n");
+ xml.append(" folder=\"test_folder\"\n");
+ xml.append(" old_folder=\"test_old_folder\"\n");
+ xml.append(" msg_type=\"MMS\"\n");
+ xml.append("/>\n");
+ DataInputStream stream = new DataInputStream(
+ new ByteArrayInputStream(xml.toString().getBytes()));
+
+ byte[] applicationParameter = new byte[] {
+ Request.OAP_TAGID_MAS_INSTANCE_ID,
+ 1, // length in byte
+ (byte) 55
+ };
+
+ HeaderSet headerSet = new HeaderSet();
+ headerSet.setHeader(HeaderSet.TYPE, MnsObexServer.TYPE);
+ headerSet.setHeader(HeaderSet.APPLICATION_PARAMETER, applicationParameter);
+
+ Operation op = mock(Operation.class);
+ when(op.getReceivedHeader()).thenReturn(headerSet);
+ when(op.openDataInputStream()).thenReturn(stream);
+
+ assertThat(mServer.onPut(op)).isEqualTo(ResponseCodes.OBEX_HTTP_OK);
+
+ verify(mStateMachine).receiveEvent(any());
+ }
+
+ @Test
+ public void onAbort_returnsNotImplemented() {
+ HeaderSet request = new HeaderSet();
+ HeaderSet reply = new HeaderSet();
+
+ assertThat(mServer.onAbort(request, reply))
+ .isEqualTo(ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED);
+ }
+
+ @Test
+ public void onSetPath_returnsBadRequest() {
+ HeaderSet request = new HeaderSet();
+ HeaderSet reply = new HeaderSet();
+
+ assertThat(mServer.onSetPath(request, reply, false, false))
+ .isEqualTo(ResponseCodes.OBEX_HTTP_BAD_REQUEST);
+ }
+
+ @Test
+ public void onClose_doesNotCrash() {
+ mServer.onClose();
+ }
+}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/mcp/MediaControlGattServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/mcp/MediaControlGattServiceTest.java
index ff7406a..d840126 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/mcp/MediaControlGattServiceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/mcp/MediaControlGattServiceTest.java
@@ -90,6 +90,7 @@
mAdapter = BluetoothAdapter.getDefaultAdapter();
doReturn(true).when(mMockGattServer).addService(any(BluetoothGattService.class));
+ doReturn(new BluetoothDevice[0]).when(mAdapterService).getBondedDevices();
mMcpService = new MediaControlGattService(mMockMcpService, mMockMcsCallbacks, TEST_CCID);
mMcpService.setBluetoothGattServerForTesting(mMockGattServer);
@@ -122,7 +123,7 @@
List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
devices.add(mCurrentDevice);
doReturn(devices).when(mMockGattServer).getConnectedDevices();
- mMcpService.setCcc(mCurrentDevice, characteristic.getUuid(), 0, value);
+ mMcpService.setCcc(mCurrentDevice, characteristic.getUuid(), 0, value, true);
}
@Test
diff --git a/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppLauncherActivityTest.java b/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppLauncherActivityTest.java
index e16e310..d9dbbb1 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppLauncherActivityTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppLauncherActivityTest.java
@@ -33,6 +33,7 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.spy;
@@ -187,7 +188,6 @@
assertThat(file.length()).isGreaterThan(shareContent.length());
}
- @Ignore("b/263754734")
@Test
public void sendFileInfo_finishImmediately() throws Exception {
doReturn(true).when(mMethodProxy).bluetoothAdapterIsEnabled(any());
@@ -195,7 +195,7 @@
mIntent.setAction("unsupported-action");
ActivityScenario<BluetoothOppLauncherActivity> scenario = ActivityScenario.launch(mIntent);
doThrow(new IllegalArgumentException()).when(mBluetoothOppManager).saveSendingFileInfo(
- any(), any(String.class), any(), any());
+ any(), any(String.class), anyBoolean(), anyBoolean());
scenario.onActivity(activity -> {
activity.sendFileInfo("text/plain", "content:///abc.txt", false, false);
});
@@ -208,18 +208,4 @@
Thread.sleep(2_000);
assertThat(activityScenario.getState()).isEqualTo(state);
}
-
-
- private void enableActivity(boolean enable) {
- int enabledState = enable ? COMPONENT_ENABLED_STATE_ENABLED
- : COMPONENT_ENABLED_STATE_DEFAULT;
-
- mTargetContext.getPackageManager().setApplicationEnabledSetting(
- mTargetContext.getPackageName(), enabledState, DONT_KILL_APP);
-
- ComponentName activityName = new ComponentName(mTargetContext,
- BluetoothOppLauncherActivity.class);
- mTargetContext.getPackageManager().setComponentEnabledSetting(
- activityName, enabledState, DONT_KILL_APP);
- }
}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppTransferHistoryTest.java b/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppTransferHistoryTest.java
index e8fd829..56c5621 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppTransferHistoryTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppTransferHistoryTest.java
@@ -48,6 +48,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -166,6 +167,7 @@
matches(isDisplayed()));
}
+ @Ignore("b/268424815")
@Test
public void onOptionsItemSelected_clearAllSelected_promptWarning() {
BluetoothOppTestUtils.setUpMockCursor(mCursor, mCursorMockDataList);
diff --git a/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppTransferTest.java b/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppTransferTest.java
index 35042b4..f1e2a77 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppTransferTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppTransferTest.java
@@ -21,6 +21,7 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doAnswer;
@@ -28,10 +29,15 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothUuid;
import android.content.ContentValues;
import android.content.Context;
import android.content.ContextWrapper;
+import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.os.Looper;
@@ -51,7 +57,8 @@
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
+
+import java.util.Objects;
@MediumTest
@RunWith(AndroidJUnit4.class)
@@ -72,8 +79,8 @@
@Mock
BluetoothOppObexSession mSession;
- @Spy
- BluetoothMethodProxy mCallProxy = BluetoothMethodProxy.getInstance();
+ @Mock
+ BluetoothMethodProxy mCallProxy;
Context mContext;
BluetoothOppBatch mBluetoothOppBatch;
BluetoothOppTransfer mTransfer;
@@ -257,4 +264,84 @@
verify(mContext).sendBroadcast(argThat(
arg -> arg.getAction().equals(BluetoothShare.USER_CONFIRMATION_TIMEOUT_ACTION)));
}
+
+ @Test
+ public void socketConnectThreadConstructors() {
+ String address = "AA:BB:CC:EE:DD:11";
+ BluetoothDevice device = (mContext.getSystemService(BluetoothManager.class))
+ .getAdapter().getRemoteDevice(address);
+ BluetoothOppTransfer transfer = new BluetoothOppTransfer(mContext, mBluetoothOppBatch);
+ BluetoothOppTransfer.SocketConnectThread socketConnectThread =
+ transfer.new SocketConnectThread(device, true);
+ BluetoothOppTransfer.SocketConnectThread socketConnectThread2 =
+ transfer.new SocketConnectThread(device, true, false, 0);
+ assertThat(Objects.equals(socketConnectThread.mDevice, device)).isTrue();
+ assertThat(Objects.equals(socketConnectThread2.mDevice, device)).isTrue();
+ }
+
+ @Test
+ public void socketConnectThreadInterrupt() {
+ String address = "AA:BB:CC:EE:DD:11";
+ BluetoothDevice device = (mContext.getSystemService(BluetoothManager.class))
+ .getAdapter().getRemoteDevice(address);
+ BluetoothOppTransfer transfer = new BluetoothOppTransfer(mContext, mBluetoothOppBatch);
+ BluetoothOppTransfer.SocketConnectThread socketConnectThread =
+ transfer.new SocketConnectThread(device, true);
+ socketConnectThread.interrupt();
+ assertThat(socketConnectThread.mIsInterrupted).isTrue();
+ }
+
+ @Test
+ @SuppressWarnings("DoNotCall")
+ public void socketConnectThreadRun_bluetoothDisabled_connectionFailed() {
+ String address = "AA:BB:CC:EE:DD:11";
+ BluetoothDevice device = (mContext.getSystemService(BluetoothManager.class))
+ .getAdapter().getRemoteDevice(address);
+ BluetoothOppTransfer transfer = new BluetoothOppTransfer(mContext, mBluetoothOppBatch);
+ BluetoothOppTransfer.SocketConnectThread socketConnectThread =
+ transfer.new SocketConnectThread(device, true);
+ transfer.mSessionHandler = mEventHandler;
+
+ socketConnectThread.run();
+ verify(mCallProxy).handlerSendEmptyMessage(any(), eq(TRANSPORT_ERROR));
+ }
+
+ @Test
+ public void oppConnectionReceiver_onReceiveWithActionAclDisconnected_sendsConnectTimeout() {
+ BluetoothDevice device = (mContext.getSystemService(BluetoothManager.class))
+ .getAdapter().getRemoteDevice(mDestination);
+ BluetoothOppTransfer transfer = new BluetoothOppTransfer(mContext, mBluetoothOppBatch);
+ transfer.mCurrentShare = mInitShareInfo;
+ transfer.mCurrentShare.mConfirm = BluetoothShare.USER_CONFIRMATION_PENDING;
+ BluetoothOppTransfer.OppConnectionReceiver receiver = transfer.new OppConnectionReceiver();
+ Intent intent = new Intent();
+ intent.setAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+
+ transfer.mSessionHandler = mEventHandler;
+ receiver.onReceive(mContext, intent);
+ verify(mCallProxy).handlerSendEmptyMessage(any(),
+ eq(BluetoothOppObexSession.MSG_CONNECT_TIMEOUT));
+ }
+
+ @Test
+ public void oppConnectionReceiver_onReceiveWithActionSdpRecord_sendsNoMessage() {
+ BluetoothDevice device = (mContext.getSystemService(BluetoothManager.class))
+ .getAdapter().getRemoteDevice(mDestination);
+ BluetoothOppTransfer transfer = new BluetoothOppTransfer(mContext, mBluetoothOppBatch);
+ transfer.mCurrentShare = mInitShareInfo;
+ transfer.mCurrentShare.mConfirm = BluetoothShare.USER_CONFIRMATION_PENDING;
+ transfer.mDevice = device;
+ transfer.mSessionHandler = mEventHandler;
+ BluetoothOppTransfer.OppConnectionReceiver receiver = transfer.new OppConnectionReceiver();
+ Intent intent = new Intent();
+ intent.setAction(BluetoothDevice.ACTION_SDP_RECORD);
+ intent.putExtra(BluetoothDevice.EXTRA_UUID, BluetoothUuid.OBEX_OBJECT_PUSH);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+
+ receiver.onReceive(mContext, intent);
+
+ // bluetooth device name is null => skip without interaction
+ verifyNoMoreInteractions(mCallProxy);
+ }
}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/pbap/BluetoothPbapUtilsTest.java b/android/app/tests/unit/src/com/android/bluetooth/pbap/BluetoothPbapUtilsTest.java
index 6ea294f..875042f 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/pbap/BluetoothPbapUtilsTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/pbap/BluetoothPbapUtilsTest.java
@@ -48,7 +48,6 @@
import org.junit.After;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -78,13 +77,13 @@
BluetoothMethodProxy.setInstanceForTesting(mProxy);
when(mContext.getResources()).thenReturn(mResources);
- initStaticFields();
+ clearStaticFields();
}
@After
public void tearDown() {
BluetoothMethodProxy.setInstanceForTesting(null);
- initStaticFields();
+ clearStaticFields();
}
@Test
@@ -310,15 +309,11 @@
}
}
- @Ignore("b/262486295")
@Test
public void updateSecondaryVersionCounter_whenContactsAreUpdated() {
MatrixCursor contactCursor = new MatrixCursor(
new String[] {Contacts._ID, Contacts.CONTACT_LAST_UPDATED_TIMESTAMP});
contactCursor.addRow(new Object[] {"id1", Calendar.getInstance().getTimeInMillis()});
- contactCursor.addRow(new Object[] {"id2", Calendar.getInstance().getTimeInMillis()});
- contactCursor.addRow(new Object[] {"id3", Calendar.getInstance().getTimeInMillis()});
- contactCursor.addRow(new Object[] {"id4", Calendar.getInstance().getTimeInMillis()});
doReturn(contactCursor).when(mProxy).contentResolverQuery(
any(), eq(Contacts.CONTENT_URI), any(), any(), any(), any());
@@ -326,31 +321,28 @@
dataCursor.addRow(new Object[] {"id1", Phone.CONTENT_ITEM_TYPE, "01234567"});
dataCursor.addRow(new Object[] {"id1", Email.CONTENT_ITEM_TYPE, "android@android.com"});
dataCursor.addRow(new Object[] {"id1", StructuredPostal.CONTENT_ITEM_TYPE, "01234"});
- dataCursor.addRow(new Object[] {"id2", StructuredName.CONTENT_ITEM_TYPE, "And Roid"});
+ dataCursor.addRow(new Object[] {"id1", StructuredName.CONTENT_ITEM_TYPE, "And Roid"});
doReturn(dataCursor).when(mProxy).contentResolverQuery(
any(), eq(Data.CONTENT_URI), any(), any(), any(), any());
+ assertThat(BluetoothPbapUtils.sSecondaryVersionCounter).isEqualTo(0);
- HandlerThread handlerThread = new HandlerThread("BluetoothPbapUtilsTest");
- handlerThread.start();
- Handler handler = new Handler(handlerThread.getLooper());
+ BluetoothPbapUtils.sTotalContacts = 1;
+ BluetoothPbapUtils.setContactFields(BluetoothPbapUtils.TYPE_NAME, "id1",
+ "test_previous_name_before_update");
- try {
- BluetoothPbapUtils.sTotalContacts = 4;
+ BluetoothPbapUtils.updateSecondaryVersionCounter(mContext, null);
- BluetoothPbapUtils.updateSecondaryVersionCounter(mContext, handler);
-
- assertThat(BluetoothPbapUtils.sContactDataset).isNotEmpty();
- } finally {
- handlerThread.quit();
- }
+ assertThat(BluetoothPbapUtils.sSecondaryVersionCounter).isEqualTo(1);
}
- private static void initStaticFields() {
+ private static void clearStaticFields() {
BluetoothPbapUtils.sPrimaryVersionCounter = 0;
BluetoothPbapUtils.sSecondaryVersionCounter = 0;
BluetoothPbapUtils.sContactSet.clear();
+ BluetoothPbapUtils.sContactDataset.clear();
BluetoothPbapUtils.sTotalContacts = 0;
BluetoothPbapUtils.sTotalFields = 0;
BluetoothPbapUtils.sTotalSvcFields = 0;
+ BluetoothPbapUtils.sContactsLastUpdated = 0;
}
}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/telephony/BluetoothInCallServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/telephony/BluetoothInCallServiceTest.java
index 9cd29f4..9332eea 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/telephony/BluetoothInCallServiceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/telephony/BluetoothInCallServiceTest.java
@@ -1354,6 +1354,66 @@
Assert.assertFalse(mBluetoothInCallService.mOnCreateCalled);
}
+ @Test
+ public void testLeCallControlCallback_onAcceptCall_withUnknownCallId() {
+ BluetoothLeCallControlProxy callControlProxy = mock(BluetoothLeCallControlProxy.class);
+ mBluetoothInCallService.mBluetoothLeCallControl = callControlProxy;
+ BluetoothLeCallControl.Callback callback =
+ mBluetoothInCallService.mBluetoothLeCallControlCallback;
+
+ int requestId = 1;
+ UUID unknownCallId = UUID.randomUUID();
+ callback.onAcceptCall(requestId, unknownCallId);
+
+ verify(callControlProxy).requestResult(
+ requestId, BluetoothLeCallControl.RESULT_ERROR_UNKNOWN_CALL_ID);
+ }
+
+ @Test
+ public void testLeCallControlCallback_onTerminateCall_withUnknownCallId() {
+ BluetoothLeCallControlProxy callControlProxy = mock(BluetoothLeCallControlProxy.class);
+ mBluetoothInCallService.mBluetoothLeCallControl = callControlProxy;
+ BluetoothLeCallControl.Callback callback =
+ mBluetoothInCallService.mBluetoothLeCallControlCallback;
+
+ int requestId = 1;
+ UUID unknownCallId = UUID.randomUUID();
+ callback.onTerminateCall(requestId, unknownCallId);
+
+ verify(callControlProxy).requestResult(
+ requestId, BluetoothLeCallControl.RESULT_ERROR_UNKNOWN_CALL_ID);
+ }
+
+ @Test
+ public void testLeCallControlCallback_onHoldCall_withUnknownCallId() {
+ BluetoothLeCallControlProxy callControlProxy = mock(BluetoothLeCallControlProxy.class);
+ mBluetoothInCallService.mBluetoothLeCallControl = callControlProxy;
+ BluetoothLeCallControl.Callback callback =
+ mBluetoothInCallService.mBluetoothLeCallControlCallback;
+
+ int requestId = 1;
+ UUID unknownCallId = UUID.randomUUID();
+ callback.onHoldCall(requestId, unknownCallId);
+
+ verify(callControlProxy).requestResult(
+ requestId, BluetoothLeCallControl.RESULT_ERROR_UNKNOWN_CALL_ID);
+ }
+
+ @Test
+ public void testLeCallControlCallback_onUnholdCall_withUnknownCallId() {
+ BluetoothLeCallControlProxy callControlProxy = mock(BluetoothLeCallControlProxy.class);
+ mBluetoothInCallService.mBluetoothLeCallControl = callControlProxy;
+ BluetoothLeCallControl.Callback callback =
+ mBluetoothInCallService.mBluetoothLeCallControlCallback;
+
+ int requestId = 1;
+ UUID unknownCallId = UUID.randomUUID();
+ callback.onUnholdCall(requestId, unknownCallId);
+
+ verify(callControlProxy).requestResult(
+ requestId, BluetoothLeCallControl.RESULT_ERROR_UNKNOWN_CALL_ID);
+ }
+
private void addCallCapability(BluetoothCall call, int capability) {
when(call.can(capability)).thenReturn(true);
}
diff --git a/android/pandora/mmi2grpc/mmi2grpc/avrcp.py b/android/pandora/mmi2grpc/mmi2grpc/avrcp.py
index 7e1d9c2..92972dc 100644
--- a/android/pandora/mmi2grpc/mmi2grpc/avrcp.py
+++ b/android/pandora/mmi2grpc/mmi2grpc/avrcp.py
@@ -793,3 +793,169 @@
self.mediaplayer.Play()
return "OK"
+
+ @assert_description
+ def _mmi_1016(self, test: str, pts_addr: bytes, **kwargs):
+ """
+ Create an AVDTP signaling channel.
+
+ Action: Create an audio or video
+ connection with PTS.
+ """
+ self.connection = self.host.Connect(address=pts_addr).connection
+ if "TG" in test:
+ try:
+ self.source = self.a2dp.OpenSource(connection=self.connection).source
+ except RpcError:
+ pass
+ else:
+ try:
+ self.sink = self.a2dp.WaitSink(connection=self.connection).sink
+ except RpcError:
+ pass
+
+ return "OK"
+
+ @assert_description
+ def TSC_AVCTP_mmi_send_AVCT_ConnectReq(self, pts_addr: bytes, **kwargs):
+ """
+ Using the Upper Tester, send an AVCT_ConnectReq command to the IUT with
+ the following input parameter values:
+ * BD_ADDR = BD_ADDRLower_Tester
+ * PID = PIDTest_System
+
+ The IUT should then initiate an
+ L2CAP_ConnectReq.
+ """
+
+ return "OK"
+
+ @assert_description
+ def TSC_AVCTP_mmi_verify_ConnectCfm_CB(self, pts_addr: bytes, **kwargs):
+ """
+ Press 'OK' if the following conditions were met :
+
+ 1. The IUT returns
+ the following AVCT_ConnectReq output parameters to the Upper Tester:
+ * Result = 0x0000 (Event successfully registered)
+
+ 2. The IUT calls the
+ ConnectCfm_CBTest_System function in the Upper Tester with the following
+ parameters:
+ * BD_ADDR = BD_ADDRLower_Tester
+ * Connect Result =
+ 0x0000 (L2CAP Connect Request successful)
+ * Config Result = 0x0000
+ (L2CAP Configure successful)
+ * Status = L2CAP Connect Request Status
+ """
+
+ return "OK"
+
+ @assert_description
+ def TSC_AVCTP_mmi_register_DisconnectCfm_CB(self, pts_addr: bytes, **kwargs):
+ """
+ Using the Upper Tester register the function DisconnectCfm_CBTest_System
+ for callback on the AVCT_Disconnect_Cfm event by sending an
+ AVCT_EventRegistration command to the IUT with the following parameter
+ values:
+ * Event = AVCT_Disconnect_Cfm
+ * Callback =
+ DisconnectCfm_CBTest_System
+ * PID = PIDTest_System
+
+ Press 'OK' to
+ continue once the IUT has responded.
+ """
+
+ return "OK"
+
+ def TSC_AVCTP_mmi_send_AVCT_Disconnect_Req(self, test: str, pts_addr: bytes, **kwargs):
+ """
+ Using the Upper Tester send an AVCT_DisconnectReq command to the IUT
+ with the following parameter values:
+ * BD_ADDR = BD_ADDRLower_Tester
+ * PID = PIDTest_System
+
+ The IUT should then initiate an
+ L2CAP_DisconnectReq.
+ """
+ # Currently disconnect is required in TG role
+ if "TG" in test:
+ if self.connection is None:
+ self.connection = self.host.GetConnection(address=pts_addr).connection
+ time.sleep(3)
+ self.host.Disconnect(connection=self.connection)
+ self.connection = None
+
+ return "OK"
+
+ @assert_description
+ def TSC_AVCTP_mmi_verify_DisconnectCfm_CB(self, **kwargs):
+ """
+ Press 'OK' if the following conditions were met :
+
+ 1. The IUT returns
+ the following AVCT_EventRegistration output parameters to the Upper
+ Tester:
+ * Result = 0x0000 (Event successfully registered)
+
+ 2. The IUT
+ calls the DisconnectCfm_CBTest_System function in the Upper Tester with
+ the following parameter values:
+ * BD_ADDR = BD_ADDRLower_Tester
+ *
+ Disconnect Result = 0x0000 (L2CAP disconnect success)
+
+ 3. The IUT
+ returns the following AVCT_DisconnectReq output parameter values to the
+ Upper Tester:
+ * RSP = 0x0000 (Request accepted)
+ """
+
+ return "OK"
+
+ @assert_description
+ def TSC_AVCTP_mmi_send_AVCT_SendMessage_TG(self, **kwargs):
+ """
+ Upon a call to the call back function MessageInd_CBTest_System, use the
+ Upper Tester to send an AVCT_SendMessage command to the IUT with the
+ following parameter values:
+ * BD_ADDR = BD_ADDRTest_System
+ *
+ Transaction = TRANSTest_System
+ * Type = CRTest_System = 1 (Response
+ Message)
+ * PID = PIDTest_System
+ * Data = ADDRESSdata_buffer
+ (Buffer containing DATA[]Upper_Tester)
+ * Length =
+ LengthOf(DATA[]Upper_Tester) <= MTU – 3bytes
+ """
+
+ return "OK"
+
+ @assert_description
+ def TSC_AVCTP_mmi_verify_MessageInd_CB_TG(self, **kwargs):
+ """
+ Press 'OK' if the following conditions were met :
+
+ 1. The
+ MessageInd_CBTest_System function in the Upper Tester is called with the
+ following parameters:
+ * BD_ADDR = BD_ADDRLower_Tester
+ *
+ Transaction = TRANSTest_System
+ * Type = 0x00 (Command message)
+ *
+ Data = ADDRESSdata_buffer (Buffer containing DATA[]Lower_Tester)
+ *
+ Length = LengthOf(DATA[]Lower_Tester)
+
+ 2. the IUT returns the following
+ AVCT_SendMessage output parameters to the Upper Tester:
+ * Result =
+ 0x0000 (Request accepted)
+ """
+
+ return "OK"
diff --git a/android/pandora/mmi2grpc/mmi2grpc/hfp.py b/android/pandora/mmi2grpc/mmi2grpc/hfp.py
index f6ce7c9..1cf2227 100644
--- a/android/pandora/mmi2grpc/mmi2grpc/hfp.py
+++ b/android/pandora/mmi2grpc/mmi2grpc/hfp.py
@@ -18,7 +18,7 @@
from pandora_experimental.hfp_grpc import HFP
from pandora_experimental.host_grpc import Host
-from pandora_experimental.host_pb2 import ConnectabilityMode
+from pandora_experimental.host_pb2 import ConnectabilityMode, DiscoverabilityMode
from pandora_experimental.security_grpc import Security, SecurityStorage
from pandora_experimental.hfp_pb2 import AudioPath
@@ -96,7 +96,10 @@
if not self.connection:
self.connection = self.host.Connect(address=pts_addr).connection
- self.hfp.EnableSlc(connection=self.connection)
+ if "HFP/HF" in test:
+ self.hfp.EnableSlcAsHandsfree(connection=self.connection)
+ else:
+ self.hfp.EnableSlc(connection=self.connection)
threading.Thread(target=enable_slc).start()
@@ -137,7 +140,7 @@
return "OK"
@assert_description
- def TSC_iut_disable_slc(self, pts_addr: bytes, **kwargs):
+ def TSC_iut_disable_slc(self, test: str, pts_addr: bytes, **kwargs):
"""
Click Ok, then disable the service level connection using the
Implementation Under Test (IUT).
@@ -147,7 +150,10 @@
def disable_slc():
time.sleep(2)
- self.hfp.DisableSlc(connection=self.connection)
+ if "HFP/HF" in test:
+ self.hfp.DisableSlcAsHandsfree(connection=self.connection)
+ else:
+ self.hfp.DisableSlc(connection=self.connection)
threading.Thread(target=disable_slc).start()
@@ -225,16 +231,21 @@
return "OK"
@assert_description
- def TSC_iut_disable_audio(self, **kwargs):
+ def TSC_iut_disable_audio(self, test: str, pts_addr: bytes, **kwargs):
"""
Click Ok, then close the audio connection (SCO) between the
Implementation Under Test (IUT) and the PTS. Do not close the serivice
level connection (SLC) or power-off the IUT.
"""
+ self.connection = self.host.GetConnection(address=pts_addr).connection
+
def disable_audio():
time.sleep(2)
- self.hfp.SetAudioPath(audio_path=AudioPath.AUDIO_PATH_SPEAKERS)
+ if "HFP/HF" in test:
+ self.hfp.DisconnectToAudioAsHandsfree(connection=self.connection)
+ else:
+ self.hfp.SetAudioPath(audio_path=AudioPath.AUDIO_PATH_SPEAKERS)
threading.Thread(target=disable_audio).start()
@@ -249,15 +260,20 @@
return "OK"
@assert_description
- def TSC_iut_enable_audio(self, **kwargs):
+ def TSC_iut_enable_audio(self, test: str, pts_addr: bytes, **kwargs):
"""
Click Ok, then initiate an audio connection (SCO) from the
Implementation Under Test (IUT) to the PTS.
"""
+ self.connection = self.host.GetConnection(address=pts_addr).connection
+
def enable_audio():
time.sleep(2)
- self.hfp.SetAudioPath(audio_path=AudioPath.AUDIO_PATH_HANDSFREE)
+ if "HFP/HF" in test:
+ self.hfp.ConnectToAudioAsHandsfree(connection=self.connection)
+ else:
+ self.hfp.SetAudioPath(audio_path=AudioPath.AUDIO_PATH_HANDSFREE)
threading.Thread(target=enable_audio).start()
@@ -579,16 +595,26 @@
return "OK"
@assert_description
- def TSC_prepare_iut_for_vra(self, pts_addr: bytes, **kwargs):
+ def TSC_prepare_iut_for_vra(self, pts_addr: bytes, test: str, **kwargs):
"""
Place the Implementation Under Test (IUT) in a state which will allow a
request from the PTS to activate voice recognition, then click Ok.
"""
- self.hfp.SetVoiceRecognition(
- enabled=True,
- connection=self.host.GetConnection(address=pts_addr).connection,
- )
+ if "HFP/HF" not in test:
+ self.hfp.SetVoiceRecognition(
+ enabled=True,
+ connection=self.host.GetConnection(address=pts_addr).connection,
+ )
+
+ return "OK"
+
+ @assert_description
+ def TSC_prepare_iut_for_vrd(self, **kwargs):
+ """
+ Place the Implementation Under Test (IUT) in a state which will allow a
+ voice recognition deactivation from PTS, then click Ok.
+ """
return "OK"
@@ -604,20 +630,285 @@
return "OK"
@assert_description
- def TSC_reject_call(self, **kwargs):
+ def TSC_reject_call(self, test: str, pts_addr: bytes, **kwargs):
"""
Click Ok, then reject the incoming call using the Implemention Under
Test (IUT).
"""
+ self.connection = self.host.GetConnection(address=pts_addr).connection
+
def reject_call():
time.sleep(2)
- self.hfp.DeclineCall()
+ if "HFP/HF" in test:
+ self.hfp.DeclineCallAsHandsfree(connection=self.connection)
+ else:
+ self.hfp.DeclineCall()
threading.Thread(target=reject_call).start()
return "OK"
+ @assert_description
+ def TSC_hf_iut_answer_call(self, pts_addr: bytes, **kwargs):
+ """
+ Click Ok, then answer the incoming call using the Implementation Under
+ Test (IUT).
+ """
+
+ self.connection = self.host.GetConnection(address=pts_addr).connection
+
+ def answer_call():
+ time.sleep(2)
+ self.hfp.AnswerCallAsHandsfree(connection=self.connection)
+
+ threading.Thread(target=answer_call).start()
+
+ return "OK"
+
+ @assert_description
+ def TSC_iut_disable_audio_poweroff_ok(self, **kwargs):
+ """
+ Click Ok, then close the audio connection (SCO) by one of the following
+ ways:
+
+ 1. Close the service level connection (SLC)
+ 2. Powering off the
+ Implementation Under Test (IUT)
+ """
+
+ self.host.Reset()
+
+ return "OK"
+
+ @assert_description
+ def TSC_verify_inband_ring(self, **kwargs):
+ """
+ Verify that the in-band ringtone is audible, then click Ok.
+ """
+
+ return "OK"
+
+ @assert_description
+ def TSC_verify_inband_ring_muting(self, **kwargs):
+ """
+ Verify that the in-band ringtone is not audible , then click Ok.
+ """
+
+ return "OK"
+
+ @assert_description
+ def TSC_hf_iut_disable_call(self, pts_addr: bytes, **kwargs):
+ """
+ Click Ok, then end the call process from the Implementation Under Test
+ (IUT).
+ """
+
+ self.connection = self.host.GetConnection(address=pts_addr).connection
+
+ def disable_call():
+ time.sleep(2)
+ self.hfp.EndCallAsHandsfree(connection=self.connection)
+
+ threading.Thread(target=disable_call).start()
+
+ return "OK"
+
+ @assert_description
+ def TSC_mute_inband_ring_iut(self, **kwargs):
+ """
+ Mute the in-band ringtone on the Implementation Under Test (IUT) and
+ then click OK.
+ """
+
+ return "OK"
+
+ @assert_description
+ def TSC_verify_iut_alerting(self, **kwargs):
+ """
+ Verify that the Implementation Under Test (IUT) is generating a local
+ alert, then click Ok.
+ """
+
+ return "OK"
+
+ @assert_description
+ def TSC_verify_iut_not_alerting(self, **kwargs):
+ """
+ Verify that the Implementation Under Test (IUT) is not generating a
+ local alert.
+ """
+
+ return "OK"
+
+ @assert_description
+ def TSC_hf_iut_enable_call_number(self, pts_addr: bytes, **kwargs):
+ """
+ Click Ok, then place an outgoing call from the Implementation Under Test
+ (IUT) using an enterted phone number.
+ """
+
+ self.connection = self.host.GetConnection(address=pts_addr).connection
+
+ def disable_call():
+ time.sleep(2)
+ self.hfp.MakeCallAsHandsfree(connection=self.connection, number="42")
+
+ threading.Thread(target=disable_call).start()
+
+ return "OK"
+
+ @assert_description
+ def TSC_hf_iut_enable_call_memory(self, **kwargs):
+ """
+ Click Ok, then place an outgoing call from the Implementation Under Test
+ (IUT) by entering the memory index. For further clarification please
+ see the HFP 1.5 Specification.
+ """
+
+ return "OK"
+
+ @assert_description
+ def TSC_hf_iut_call_swap_then_disable_held_alternative(self, pts_addr: bytes, **kwargs):
+ """
+ Using the Implementation Under Test (IUT), perform one of the following
+ two actions:
+
+ 1. Click OK, make the held/waiting call active, disabling
+ the active call.
+ 2. Click OK, make the held/waiting call active, placing
+ the active call on hold.
+ """
+
+ self.connection = self.host.GetConnection(address=pts_addr).connection
+
+ def call_swap_then_disable_held_alternative():
+ time.sleep(2)
+ self.hfp.CallTransferAsHandsfree(connection=self.connection)
+
+ threading.Thread(target=call_swap_then_disable_held_alternative).start()
+
+ return "OK"
+
+ @assert_description
+ def TSC_iut_make_discoverable(self, **kwargs):
+ """
+ Place the Implementation Under Test (IUT) in discoverable mode, then
+ click Ok.
+ """
+
+ self.host.SetDiscoverabilityMode(mode=DiscoverabilityMode.DISCOVERABLE_GENERAL)
+
+ return "OK"
+
+ @assert_description
+ def TSC_iut_accept_connection(self, **kwargs):
+ """
+ Click Ok, then accept the pairing and connection requests on the
+ Implementation Under Test (IUT), if prompted.
+ """
+
+ return "OK"
+
+ @assert_description
+ def TSC_voice_recognition_enable_iut(self, pts_addr: bytes, **kwargs):
+ """
+ Using the Implementation Under Test (IUT), activate voice recognition.
+ """
+
+ self.hfp.SetVoiceRecognitionAsHandsfree(
+ enabled=True,
+ connection=self.host.GetConnection(address=pts_addr).connection,
+ )
+
+ return "OK"
+
+ @assert_description
+ def TSC_voice_recognition_disable_iut(self, pts_addr: bytes, **kwargs):
+ """
+ Using the Implementation Under Test (IUT), deactivate voice recognition.
+ """
+
+ self.hfp.SetVoiceRecognitionAsHandsfree(
+ enabled=False,
+ connection=self.host.GetConnection(address=pts_addr).connection,
+ )
+
+ return "OK"
+
+ @match_description
+ def TSC_dtmf_send(self, pts_addr: bytes, dtmf: str, **kwargs):
+ r"""
+ Send the DTMF code, then click Ok. (?P<dtmf>.*)
+ """
+
+ self.hfp.SendDtmfFromHandsfree(
+ connection=self.host.GetConnection(address=pts_addr).connection,
+ code=dtmf[0].encode("ascii")[0],
+ )
+
+ return "OK"
+
+ @assert_description
+ def TSC_verify_hf_iut_reports_held_and_active_call(self, **kwargs):
+ """
+ Verify that the Implementation Under Test (IUT) interprets both held and
+ active call signals, then click Ok. If applicable, verify that the
+ information is correctly displayed on the IUT, then click Ok.
+ """
+
+ return "OK"
+
+ def TSC_rf_shield_iut_or_pts(self, **kwargs):
+ """
+ Click Ok, then move the PTS and the Implementation Under Test (IUT) out
+ of range of each other by performing one of the following IUT specific
+ actions:
+
+ 1. Hands Free (HF) IUT - Place the IUT in the RF shield box or
+ physically take out of range from the PTS.
+
+ 2. Audio Gateway (AG) IUT-
+ Physically take the IUT out range. Do not place in the RF shield box as
+ it will interfere with the cellular network.
+
+ Note: The PTS can also be
+ placed in the RF shield box if necessary.
+ """
+
+ def shield_iut_or_pts():
+ time.sleep(2)
+ self.rootcanal.disconnect_phy()
+
+ threading.Thread(target=shield_iut_or_pts).start()
+
+ return "OK"
+
+ @assert_description
+ def TSC_rf_shield_open(self, **kwargs):
+ """
+ Click Ok, then remove the Implementation Under Test (IUT) and/or the PTS
+ from the RF shield. If the out of range method was used, bring the IUT
+ and PTS back within range.
+ """
+
+ def shield_open():
+ time.sleep(2)
+ self.rootcanal.reconnect_phy_if_needed()
+
+ threading.Thread(target=shield_open).start()
+
+ return "OK"
+
+ @match_description
+ def TSC_verify_speaker_volume(self, volume: str, **kwargs):
+ r"""
+ Verify that the Hands Free \(HF\) speaker volume is displayed correctly on
+ the Implementation Under Test \(IUT\).(?P<volume>[0-9]*)
+ """
+
+ return "OK"
+
def _auto_confirm_requests(self, times=None):
def task():
diff --git a/android/pandora/mmi2grpc/mmi2grpc/l2cap.py b/android/pandora/mmi2grpc/mmi2grpc/l2cap.py
index 561c600..b6db482 100644
--- a/android/pandora/mmi2grpc/mmi2grpc/l2cap.py
+++ b/android/pandora/mmi2grpc/mmi2grpc/l2cap.py
@@ -16,6 +16,7 @@
class L2CAPProxy(ProfileProxy):
test_status_map = {} # record test status and pass them between MMI
LE_DATA_PACKET_LARGE = "data: LE_DATA_PACKET_LARGE"
+ LE_DATA_PACKET1 = "data: LE_PACKET1"
connection: Optional[Connection] = None
def __init__(self, channel):
@@ -101,6 +102,10 @@
)
# not strictly necessary, but can save time on waiting connection
tests_to_open_bluetooth_server_socket = [
+ "L2CAP/COS/CFC/BV-01-C",
+ "L2CAP/COS/CFC/BV-02-C",
+ "L2CAP/COS/CFC/BV-03-C",
+ "L2CAP/COS/CFC/BV-04-C",
"L2CAP/LE/CFC/BV-03-C",
"L2CAP/LE/CFC/BV-05-C",
"L2CAP/LE/CFC/BV-06-C",
@@ -138,7 +143,7 @@
return "OK"
@match_description
- def MMI_UPPER_TESTER_CONFIRM_LE_DATA(self, sent_data: str, **kwargs):
+ def MMI_UPPER_TESTER_CONFIRM_LE_DATA(self, sent_data: str, test: str, **kwargs):
"""
Did the Upper Tester send the data (?P<sent_data>[0-9A-F]*) to to the
PTS\? Click Yes if it matched, otherwise click No.
@@ -146,10 +151,13 @@
Description: The Implementation Under Test
\(IUT\) send data is receive correctly in the PTS.
"""
- hex_LE_DATA_PACKET_LARGE = self.LE_DATA_PACKET_LARGE.encode("utf-8").hex().upper()
- if sent_data != hex_LE_DATA_PACKET_LARGE:
- print(f"data not match, sent_data:{sent_data} and {hex_LE_DATA_PACKET_LARGE}", file=sys.stderr)
- raise Exception(f"data not match, sent_data:{sent_data} and {hex_LE_DATA_PACKET_LARGE}")
+ if test == 'L2CAP/COS/CFC/BV-02-C':
+ hex_LE_DATA_PACKET = self.LE_DATA_PACKET1.encode("utf-8").hex().upper()
+ else:
+ hex_LE_DATA_PACKET = self.LE_DATA_PACKET_LARGE.encode("utf-8").hex().upper()
+ if sent_data != hex_LE_DATA_PACKET:
+ print(f"data not match, sent_data:{sent_data} and {hex_LE_DATA_PACKET}", file=sys.stderr)
+ raise Exception(f"data not match, sent_data:{sent_data} and {hex_LE_DATA_PACKET}")
return "OK"
@assert_description
@@ -424,3 +432,23 @@
"""
return "OK"
+
+ @assert_description
+ def MMI_UPPER_TESTER_SEND_LE_DATA_PACKET1(self, **kwargs):
+ """
+ Upper Tester command IUT to send a non-segmented LE data packet to the
+ PTS with any values.
+ Description : The Implementation Under Test(IUT)
+ should send none segmantation LE frame of LE data to the PTS.
+ """
+ self.l2cap.SendData(connection=self.connection, data=bytes(self.LE_DATA_PACKET1, "utf-8"))
+ return "OK"
+
+ @assert_description
+ def MMI_IUT_SEND_L2CAP_DATA(self, **kwargs):
+ """
+ Using the Implementation Under Test(IUT), send L2CAP_Data over the
+ assigned channel with correct DCID to the PTS.
+ """
+
+ return "OK"
diff --git a/android/pandora/server/configs/PtsBotTest.xml b/android/pandora/server/configs/PtsBotTest.xml
index feb8524..e748a2e 100644
--- a/android/pandora/server/configs/PtsBotTest.xml
+++ b/android/pandora/server/configs/PtsBotTest.xml
@@ -38,9 +38,11 @@
<option name="profile" value="GAP" />
<option name="profile" value="GATT" />
<option name="profile" value="HFP/AG" />
+ <option name="profile" value="HFP/HF" />
<option name="profile" value="HID/HOS" />
<option name="profile" value="HOGP" />
<option name="profile" value="L2CAP/COS" />
+ <option name="profile" value="L2CAP/EXF" />
<option name="profile" value="L2CAP/LE" />
<option name="profile" value="MAP" />
<option name="profile" value="OPP" />
diff --git a/android/pandora/server/configs/PtsBotTestMts.xml b/android/pandora/server/configs/PtsBotTestMts.xml
index 5df935c..127cb4f 100644
--- a/android/pandora/server/configs/PtsBotTestMts.xml
+++ b/android/pandora/server/configs/PtsBotTestMts.xml
@@ -38,9 +38,11 @@
<option name="profile" value="GAP" />
<option name="profile" value="GATT" />
<option name="profile" value="HFP/AG" />
+ <option name="profile" value="HFP/HF" />
<option name="profile" value="HID/HOS" />
<option name="profile" value="HOGP" />
<option name="profile" value="L2CAP/COS" />
+ <option name="profile" value="L2CAP/EXF" />
<option name="profile" value="L2CAP/LE" />
<option name="profile" value="MAP" />
<option name="profile" value="OPP" />
diff --git a/android/pandora/server/configs/pts_bot_tests_config.json b/android/pandora/server/configs/pts_bot_tests_config.json
index 2421238..19b572f 100644
--- a/android/pandora/server/configs/pts_bot_tests_config.json
+++ b/android/pandora/server/configs/pts_bot_tests_config.json
@@ -25,10 +25,13 @@
"A2DP/SRC/SUS/BV-01-I",
"AVCTP/CT/CCM/BV-03-C",
"AVCTP/CT/CCM/BV-04-C",
+ "AVCTP/TG/CCM/BV-01-C",
+ "AVCTP/TG/CCM/BV-02-C",
"AVCTP/TG/CCM/BV-03-C",
"AVCTP/TG/CCM/BV-04-C",
"AVCTP/TG/FRA/BV-03-C",
"AVCTP/TG/NFR/BI-01-C",
+ "AVCTP/TG/NFR/BV-02-C",
"AVCTP/TG/NFR/BV-03-C",
"AVDTP/SNK/ACP/SIG/SMG/BI-05-C",
"AVDTP/SNK/ACP/SIG/SMG/BI-08-C",
@@ -316,15 +319,26 @@
"HOGP/RH/HGRF/BV-10-I",
"HOGP/RH/HGRF/BV-12-I",
"L2CAP/COS/CED/BI-01-C",
+ "L2CAP/COS/CED/BV-03-C",
"L2CAP/COS/CED/BV-05-C",
"L2CAP/COS/CED/BV-07-C",
"L2CAP/COS/CED/BV-08-C",
"L2CAP/COS/CED/BV-11-C",
+ "L2CAP/COS/CFC/BV-01-C",
+ "L2CAP/COS/CFC/BV-02-C",
+ "L2CAP/COS/CFC/BV-03-C",
+ "L2CAP/COS/CFC/BV-04-C",
"L2CAP/COS/CFD/BV-02-C",
"L2CAP/COS/CFD/BV-03-C",
"L2CAP/COS/CFD/BV-11-C",
"L2CAP/COS/CFD/BV-12-C",
"L2CAP/COS/CFD/BV-14-C",
+ "L2CAP/COS/ECH/BV-01-C",
+ "L2CAP/COS/IEX/BV-02-C",
+ "L2CAP/EXF/BV-01-C",
+ "L2CAP/EXF/BV-02-C",
+ "L2CAP/EXF/BV-03-C",
+ "L2CAP/EXF/BV-05-C",
"L2CAP/LE/CFC/BI-01-C",
"L2CAP/LE/CFC/BV-01-C",
"L2CAP/LE/CFC/BV-02-C",
@@ -540,10 +554,7 @@
"AVCTP/CT/FRA/BV-04-C",
"AVCTP/CT/NFR/BV-01-C",
"AVCTP/CT/NFR/BV-04-C",
- "AVCTP/TG/CCM/BV-01-C",
- "AVCTP/TG/CCM/BV-02-C",
"AVCTP/TG/FRA/BV-02-C",
- "AVCTP/TG/NFR/BV-02-C",
"AVDTP/SNK/ACP/SIG/SMG/BI-11-C",
"AVDTP/SNK/ACP/SIG/SMG/BI-23-C",
"AVDTP/SNK/ACP/SIG/SMG/BV-14-C",
@@ -737,10 +748,24 @@
"HFP/AG/HFI/BI-03-I",
"HFP/AG/OCN/BV-01-I",
"HFP/AG/SLC/BV-04-C",
+ "HFP/HF/OCM/BV-01-I",
+ "HFP/HF/OCM/BV-02-I",
+ "HFP/HF/OCL/BV-01-I",
+ "HFP/HF/OCL/BV-02-I",
+ "HFP/HF/TWC/BV-02-I",
+ "HFP/HF/TWC/BV-03-I",
+ "HFP/HF/ENO/BV-01-I",
+ "HFP/HF/VRD/BV-01-I",
+ "HFP/HF/NUM/BV-01-I",
+ "HFP/HF/NUM/BI-01-I",
+ "HFP/HF/ACC/BV-01-I",
+ "HFP/HF/ACC/BV-02-I",
+ "HFP/HF/ECC/BV-01-I",
+ "HFP/HF/ECC/BV-02-I",
+ "HFP/HF/ECS/BV-01-I",
"HID/HOS/HCR/BV-01-I",
"L2CAP/COS/CED/BI-02-C",
"L2CAP/COS/CED/BV-01-C",
- "L2CAP/COS/CED/BV-03-C",
"L2CAP/COS/CED/BV-04-C",
"L2CAP/COS/CED/BV-09-C",
"L2CAP/COS/CED/BV-10-C",
@@ -751,14 +776,9 @@
"L2CAP/COS/CFD/BV-08-C",
"L2CAP/COS/CFD/BV-10-C",
"L2CAP/COS/CFD/BV-13-C",
- "L2CAP/COS/CFC/BV-01-C",
- "L2CAP/COS/CFC/BV-02-C",
- "L2CAP/COS/CFC/BV-03-C",
- "L2CAP/COS/CFC/BV-04-C",
"L2CAP/COS/CFC/BV-05-C",
"L2CAP/COS/ECH/BV-02-C",
"L2CAP/COS/IEX/BV-01-C",
- "L2CAP/COS/IEX/BV-02-C",
"L2CAP/LE/CFC/BV-07-C",
"L2CAP/LE/CFC/BV-11-C",
"L2CAP/LE/CFC/BV-13-C",
@@ -1411,8 +1431,11 @@
"TSPC_HFP_3_14": true,
"TSPC_HFP_3_15": true,
"TSPC_HFP_3_17": true,
+ "TSPC_HFP_3_18a": true,
+ "TSPC_HFP_3_18c": true,
"TSPC_HFP_3_20": true,
"TSPC_HFP_3_21a": true,
+ "TSPC_HFP_3_21b": true,
"TSPC_HFP_3_23": true,
"TSPC_HFP_3_24": true,
"TSPC_HFP_3_26": true,
diff --git a/android/pandora/server/src/com/android/pandora/HfpHandsfree.kt b/android/pandora/server/src/com/android/pandora/HfpHandsfree.kt
new file mode 100644
index 0000000..f2af500
--- /dev/null
+++ b/android/pandora/server/src/com/android/pandora/HfpHandsfree.kt
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.pandora
+
+import android.annotation.SuppressLint
+import android.bluetooth.BluetoothDevice
+import android.bluetooth.BluetoothHeadset
+import android.bluetooth.BluetoothHeadsetClient
+import android.bluetooth.BluetoothManager
+import android.bluetooth.BluetoothProfile
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.net.Uri
+import android.os.Bundle
+import android.os.IBinder
+import android.provider.CallLog
+import android.telecom.Call
+import android.telecom.CallAudioState
+import android.telecom.InCallService
+import android.telecom.TelecomManager
+import android.telecom.VideoProfile
+import com.google.protobuf.Empty
+import io.grpc.stub.StreamObserver
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.shareIn
+import pandora.HFPGrpc.HFPImplBase
+import pandora.HfpProto.*
+
+private const val TAG = "PandoraHfpHandsfree"
+
+@kotlinx.coroutines.ExperimentalCoroutinesApi
+class HfpHandsfree(val context: Context) : HFPImplBase() {
+ private val scope: CoroutineScope = CoroutineScope(Dispatchers.Default)
+ private val flow: Flow<Intent>
+
+ private val bluetoothManager = context.getSystemService(BluetoothManager::class.java)!!
+ private val telecomManager = context.getSystemService(TelecomManager::class.java)!!
+ private val bluetoothAdapter = bluetoothManager.adapter
+
+ private val bluetoothHfpClient = getProfileProxy<BluetoothHeadsetClient>(context, BluetoothProfile.HEADSET_CLIENT)
+
+ companion object {
+ @SuppressLint("StaticFieldLeak") private lateinit var inCallService: InCallService
+ }
+
+ init {
+ val intentFilter = IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED)
+ flow = intentFlow(context, intentFilter).shareIn(scope, SharingStarted.Eagerly)
+ }
+
+ fun deinit() {
+ bluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET_CLIENT, bluetoothHfpClient)
+ scope.cancel()
+ }
+
+ override fun answerCallAsHandsfree(
+ request: AnswerCallAsHandsfreeRequest,
+ responseObserver: StreamObserver<AnswerCallAsHandsfreeResponse>
+ ) {
+ grpcUnary(scope, responseObserver) {
+ bluetoothHfpClient.acceptCall(request.connection.toBluetoothDevice(bluetoothAdapter), BluetoothHeadsetClient.CALL_ACCEPT_NONE)
+ AnswerCallAsHandsfreeResponse.getDefaultInstance()
+ }
+ }
+
+ override fun endCallAsHandsfree(
+ request: EndCallAsHandsfreeRequest,
+ responseObserver: StreamObserver<EndCallAsHandsfreeResponse>
+ ) {
+ grpcUnary(scope, responseObserver) {
+ for (call in bluetoothHfpClient.getCurrentCalls(request.connection.toBluetoothDevice(bluetoothAdapter))) {
+ bluetoothHfpClient.terminateCall(request.connection.toBluetoothDevice(bluetoothAdapter), call)
+ }
+ EndCallAsHandsfreeResponse.getDefaultInstance()
+ }
+ }
+
+ override fun declineCallAsHandsfree(
+ request: DeclineCallAsHandsfreeRequest,
+ responseObserver: StreamObserver<DeclineCallAsHandsfreeResponse>
+ ) {
+ grpcUnary(scope, responseObserver) {
+ bluetoothHfpClient.rejectCall(request.connection.toBluetoothDevice(bluetoothAdapter))
+ DeclineCallAsHandsfreeResponse.getDefaultInstance()
+ }
+ }
+
+ override fun connectToAudioAsHandsfree(
+ request: ConnectToAudioAsHandsfreeRequest,
+ responseObserver: StreamObserver<ConnectToAudioAsHandsfreeResponse>
+ ) {
+ grpcUnary(scope, responseObserver) {
+ bluetoothHfpClient.connectAudio(request.connection.toBluetoothDevice(bluetoothAdapter))
+ ConnectToAudioAsHandsfreeResponse.getDefaultInstance()
+ }
+ }
+
+ override fun disconnectFromAudioAsHandsfree(
+ request: DisconnectFromAudioAsHandsfreeRequest,
+ responseObserver: StreamObserver<DisconnectFromAudioAsHandsfreeResponse>
+ ) {
+ grpcUnary(scope, responseObserver) {
+ bluetoothHfpClient.disconnectAudio(request.connection.toBluetoothDevice(bluetoothAdapter))
+ DisconnectFromAudioAsHandsfreeResponse.getDefaultInstance()
+ }
+ }
+
+ override fun makeCallAsHandsfree(
+ request: MakeCallAsHandsfreeRequest,
+ responseObserver: StreamObserver<MakeCallAsHandsfreeResponse>
+ ) {
+ grpcUnary(scope, responseObserver) {
+ bluetoothHfpClient.dial(request.connection.toBluetoothDevice(bluetoothAdapter), request.number)
+ MakeCallAsHandsfreeResponse.getDefaultInstance()
+ }
+ }
+
+ override fun callTransferAsHandsfree(
+ request: CallTransferAsHandsfreeRequest,
+ responseObserver: StreamObserver<CallTransferAsHandsfreeResponse>
+ ) {
+ grpcUnary(scope, responseObserver) {
+ bluetoothHfpClient.explicitCallTransfer(request.connection.toBluetoothDevice(bluetoothAdapter))
+ CallTransferAsHandsfreeResponse.getDefaultInstance()
+ }
+ }
+
+ override fun enableSlcAsHandsfree(
+ request: EnableSlcAsHandsfreeRequest,
+ responseObserver: StreamObserver<Empty>
+ ) {
+ grpcUnary(scope, responseObserver) {
+ bluetoothHfpClient.setConnectionPolicy(request.connection.toBluetoothDevice(bluetoothAdapter), BluetoothProfile.CONNECTION_POLICY_ALLOWED)
+ Empty.getDefaultInstance()
+ }
+ }
+
+ override fun disableSlcAsHandsfree(
+ request: DisableSlcAsHandsfreeRequest,
+ responseObserver: StreamObserver<Empty>
+ ) {
+ grpcUnary(scope, responseObserver) {
+ bluetoothHfpClient.setConnectionPolicy(request.connection.toBluetoothDevice(bluetoothAdapter), BluetoothProfile.CONNECTION_POLICY_FORBIDDEN)
+ Empty.getDefaultInstance()
+ }
+ }
+
+ override fun setVoiceRecognitionAsHandsfree(
+ request: SetVoiceRecognitionAsHandsfreeRequest,
+ responseObserver: StreamObserver<SetVoiceRecognitionAsHandsfreeResponse>
+ ) {
+ grpcUnary(scope, responseObserver) {
+ if (request.enabled) {
+ bluetoothHfpClient.startVoiceRecognition(request.connection.toBluetoothDevice(bluetoothAdapter))
+ } else {
+ bluetoothHfpClient.stopVoiceRecognition(request.connection.toBluetoothDevice(bluetoothAdapter))
+ }
+ SetVoiceRecognitionAsHandsfreeResponse.getDefaultInstance()
+ }
+ }
+
+ override fun sendDtmfFromHandsfree(
+ request: SendDtmfFromHandsfreeRequest,
+ responseObserver: StreamObserver<SendDtmfFromHandsfreeResponse>
+ ) {
+ grpcUnary(scope, responseObserver) {
+ bluetoothHfpClient.sendDTMF(request.connection.toBluetoothDevice(bluetoothAdapter), request.code.toByte())
+ SendDtmfFromHandsfreeResponse.getDefaultInstance()
+ }
+ }
+}
diff --git a/android/pandora/server/src/com/android/pandora/Server.kt b/android/pandora/server/src/com/android/pandora/Server.kt
index c4f95ca..7b17dd4 100644
--- a/android/pandora/server/src/com/android/pandora/Server.kt
+++ b/android/pandora/server/src/com/android/pandora/Server.kt
@@ -34,7 +34,8 @@
private var a2dpSink: A2dpSink? = null
private var avrcp: Avrcp
private var gatt: Gatt
- private var hfp: Hfp
+ private var hfp: Hfp? = null
+ private var hfpHandsfree: HfpHandsfree? = null
private var hid: Hid
private var l2cap: L2cap
private var mediaplayer: MediaPlayer
@@ -50,7 +51,6 @@
host = Host(context, security, this)
avrcp = Avrcp(context)
gatt = Gatt(context)
- hfp = Hfp(context)
hid = Hid(context)
l2cap = L2cap(context)
mediaplayer = MediaPlayer(context)
@@ -64,7 +64,6 @@
.addService(host)
.addService(avrcp)
.addService(gatt)
- .addService(hfp)
.addService(hid)
.addService(l2cap)
.addService(mediaplayer)
@@ -84,6 +83,15 @@
grpcServerBuilder.addService(a2dpSink!!)
}
+ val is_hfp_hf = bluetoothAdapter.getSupportedProfiles().contains(BluetoothProfile.HEADSET_CLIENT)
+ if (is_hfp_hf) {
+ hfpHandsfree = HfpHandsfree(context)
+ grpcServerBuilder.addService(hfpHandsfree!!)
+ } else {
+ hfp = Hfp(context)
+ grpcServerBuilder.addService(hfp!!)
+ }
+
grpcServer = grpcServerBuilder.build()
Log.d(TAG, "Starting Pandora Server")
@@ -101,7 +109,8 @@
a2dpSink?.deinit()
avrcp.deinit()
gatt.deinit()
- hfp.deinit()
+ hfp?.deinit()
+ hfpHandsfree?.deinit()
hid.deinit()
l2cap.deinit()
mediaplayer.deinit()
diff --git a/framework/java/android/bluetooth/BluetoothDevice.java b/framework/java/android/bluetooth/BluetoothDevice.java
index f7ab590..58895ef 100644
--- a/framework/java/android/bluetooth/BluetoothDevice.java
+++ b/framework/java/android/bluetooth/BluetoothDevice.java
@@ -510,7 +510,9 @@
METADATA_UNTETHERED_CASE_LOW_BATTERY_THRESHOLD,
METADATA_SPATIAL_AUDIO,
METADATA_FAST_PAIR_CUSTOMIZED_FIELDS,
- METADATA_LE_AUDIO})
+ METADATA_LE_AUDIO,
+ METADATA_GMCS_CCCD,
+ METADATA_GTBS_CCCD})
@Retention(RetentionPolicy.SOURCE)
public @interface MetadataKey{}
@@ -663,6 +665,21 @@
public static final int METADATA_ENHANCED_SETTINGS_UI_URI = 16;
/**
+ * @hide
+ */
+ public static final String COMPANION_TYPE_PRIMARY = "COMPANION_PRIMARY";
+
+ /**
+ * @hide
+ */
+ public static final String COMPANION_TYPE_SECONDARY = "COMPANION_SECONDARY";
+
+ /**
+ * @hide
+ */
+ public static final String COMPANION_TYPE_NONE = "COMPANION_NONE";
+
+ /**
* Type of the Bluetooth device, must be within the list of
* BluetoothDevice.DEVICE_TYPE_*
* Data type should be {@String} as {@link Byte} array.
@@ -742,6 +759,21 @@
*/
public static final int METADATA_LE_AUDIO = 26;
+ /**
+ * The UUIDs (16-bit) of registered to CCC characteristics from Media Control services.
+ * Data type should be {@link Byte} array.
+ * @hide
+ */
+ public static final int METADATA_GMCS_CCCD = 27;
+
+ /**
+ * The UUIDs (16-bit) of registered to CCC characteristics from Telephony Bearer service.
+ * Data type should be {@link Byte} array.
+ * @hide
+ */
+ public static final int METADATA_GTBS_CCCD = 28;
+
+ private static final int METADATA_MAX_KEY = METADATA_GTBS_CCCD;
/**
* Device type which is used in METADATA_DEVICE_TYPE
@@ -3210,7 +3242,165 @@
* @hide
*/
public static @MetadataKey int getMaxMetadataKey() {
- return METADATA_LE_AUDIO;
+ return METADATA_MAX_KEY;
+ }
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ prefix = { "FEATURE_" },
+ value = {
+ /** Remote support status of audio policy feature is unknown/unconfigured **/
+ BluetoothStatusCodes.FEATURE_NOT_CONFIGURED,
+ /** Remote support status of audio policy feature is supported **/
+ BluetoothStatusCodes.FEATURE_SUPPORTED,
+ /** Remote support status of audio policy feature is not supported **/
+ BluetoothStatusCodes.FEATURE_NOT_SUPPORTED,
+ }
+ )
+
+ public @interface AudioPolicyRemoteSupport {}
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {
+ BluetoothStatusCodes.SUCCESS,
+ BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED,
+ BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ALLOWED,
+ BluetoothStatusCodes.ERROR_DEVICE_NOT_BONDED,
+ BluetoothStatusCodes.ERROR_MISSING_BLUETOOTH_CONNECT_PERMISSION,
+ BluetoothStatusCodes.ERROR_PROFILE_NOT_CONNECTED,
+ })
+ public @interface AudioPolicyReturnValues{}
+
+ /**
+ * Returns whether the audio policy feature is supported by
+ * both the local and the remote device.
+ * This is configured during initiating the connection between the devices through
+ * one of the transport protocols (e.g. HFP Vendor specific protocol). So if the API
+ * is invoked before this initial configuration is completed, it returns
+ * {@link BluetoothStatusCodes#FEATURE_NOT_CONFIGURED} to indicate the remote
+ * device has not yet relayed this information. After the internal configuration,
+ * the support status will be set to either
+ * {@link BluetoothStatusCodes#FEATURE_NOT_SUPPORTED} or
+ * {@link BluetoothStatusCodes#FEATURE_SUPPORTED}.
+ * The rest of the APIs related to this feature in both {@link BluetoothDevice}
+ * and {@link BluetoothSinkAudioPolicy} should be invoked only after getting a
+ * {@link BluetoothStatusCodes#FEATURE_SUPPORTED} response from this API.
+ * <p>Note that this API is intended to be used by a client device to send these requests
+ * to the server represented by this BluetoothDevice object.
+ *
+ * @return if call audio policy feature is supported by both local and remote
+ * device or not
+ *
+ * @hide
+ */
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+ })
+ public @AudioPolicyRemoteSupport int isRequestAudioPolicyAsSinkSupported() {
+ if (DBG) log("isRequestAudioPolicyAsSinkSupported()");
+ final IBluetooth service = getService();
+ final int defaultValue = BluetoothStatusCodes.FEATURE_NOT_CONFIGURED;
+ if (service == null || !isBluetoothEnabled()) {
+ Log.e(TAG, "BT not enabled. Cannot retrieve audio policy support status.");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else {
+ try {
+ final SynchronousResultReceiver<Integer> recv = SynchronousResultReceiver.get();
+ service.isRequestAudioPolicyAsSinkSupported(this, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ } catch (RemoteException e) {
+ Log.e(TAG, "", e);
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return defaultValue;
+ }
+
+ /**
+ * Sets call audio preferences and sends them to the remote device.
+ * <p>Note that the caller should check if the feature is supported by
+ * invoking {@link BluetoothDevice#isRequestAudioPolicyAsSinkSupported} first.
+ * <p>This API will throw an exception if the feature is not supported but still
+ * invoked.
+ * <p>Note that this API is intended to be used by a client device to send these requests
+ * to the server represented by this BluetoothDevice object.
+ *
+ * @param policies call audio policy preferences
+ * @return whether audio policy was requested successfully or not
+ *
+ * @hide
+ */
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+ })
+ public @AudioPolicyReturnValues int requestAudioPolicyAsSink(
+ @NonNull BluetoothSinkAudioPolicy policies) {
+ if (DBG) log("requestAudioPolicyAsSink");
+ final IBluetooth service = getService();
+ final int defaultValue = BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED;
+ if (service == null || !isBluetoothEnabled()) {
+ Log.e(TAG, "Bluetooth is not enabled. Cannot set Audio Policy.");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else {
+ try {
+ final SynchronousResultReceiver<Integer> recv = SynchronousResultReceiver.get();
+ service.requestAudioPolicyAsSink(this, policies, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
+ }
+ return defaultValue;
+ }
+
+ /**
+ * Gets the call audio preferences for the remote device.
+ * <p>Note that the caller should check if the feature is supported by
+ * invoking {@link BluetoothDevice#isRequestAudioPolicyAsSinkSupported} first.
+ * <p>This API will throw an exception if the feature is not supported but still
+ * invoked.
+ * <p>This API will return null if
+ * 1. The bleutooth service is not started yet,
+ * 2. It is invoked for a device which is not bonded, or
+ * 3. The used transport, for example, HFP Client profile is not enabled or
+ * connected yet.
+ * <p>Note that this API is intended to be used by a client device to send these requests
+ * to the server represented by this BluetoothDevice object.
+ *
+ * @return call audio policy as {@link BluetoothSinkAudioPolicy} object
+ *
+ * @hide
+ */
+ @RequiresBluetoothConnectPermission
+ @RequiresPermission(allOf = {
+ android.Manifest.permission.BLUETOOTH_CONNECT,
+ android.Manifest.permission.BLUETOOTH_PRIVILEGED,
+ })
+ public @Nullable BluetoothSinkAudioPolicy getRequestedAudioPolicyAsSink() {
+ if (DBG) log("getRequestedAudioPolicyAsSink");
+ final IBluetooth service = getService();
+ if (service == null || !isBluetoothEnabled()) {
+ Log.e(TAG, "Bluetooth is not enabled. Cannot get Audio Policy.");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else {
+ try {
+ final SynchronousResultReceiver<BluetoothSinkAudioPolicy>
+ recv = SynchronousResultReceiver.get();
+ service.getRequestedAudioPolicyAsSink(this, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
+ }
+ return null;
}
/**
diff --git a/framework/java/android/bluetooth/BluetoothServerSocket.java b/framework/java/android/bluetooth/BluetoothServerSocket.java
index bb4e354..5a23f7e3 100644
--- a/framework/java/android/bluetooth/BluetoothServerSocket.java
+++ b/framework/java/android/bluetooth/BluetoothServerSocket.java
@@ -75,7 +75,7 @@
public final class BluetoothServerSocket implements Closeable {
private static final String TAG = "BluetoothServerSocket";
- private static final boolean DBG = false;
+ private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
@UnsupportedAppUsage(publicAlternatives = "Use public {@link BluetoothServerSocket} API "
+ "instead.")
/*package*/ final BluetoothSocket mSocket;
diff --git a/framework/java/android/bluetooth/BluetoothSinkAudioPolicy.java b/framework/java/android/bluetooth/BluetoothSinkAudioPolicy.java
new file mode 100644
index 0000000..8641e51
--- /dev/null
+++ b/framework/java/android/bluetooth/BluetoothSinkAudioPolicy.java
@@ -0,0 +1,325 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * Represents Bluetooth Audio Policies of a Handsfree (HF) device (if HFP is used)
+ * and Call Terminal (CT) device (if BLE Audio is used), which describes the
+ * preferences of allowing or disallowing audio based on the use cases. The HF/CT
+ * devices shall send objects of this class to send its preference to the AG/CG
+ * devices.
+ *
+ * <p>HF/CT side applications on can use {@link BluetoothDevice#requestAudioPolicyAsSink}
+ * API to set and send a {@link BluetoothSinkAudioPolicy} object containing the
+ * preference/policy values. This object will be stored in the memory of HF/CT
+ * side, will be send to the AG/CG side using Android Specific AT Commands and will
+ * be stored in the AG side memory and database.
+ *
+ * <p>HF/CT side API {@link BluetoothDevice#getRequestedAudioPolicyAsSink} can be used to retrieve
+ * the stored audio policies currently.
+ *
+ * <p>Note that the setter APIs of this class will only set the values of the
+ * object. To actually set the policies, API {@link BluetoothDevice#requestAudioPolicyAsSink}
+ * must need to be invoked with the {@link BluetoothSinkAudioPolicy} object.
+ *
+ * <p>Note that any API related to this feature should be used after configuring
+ * the support of the AG device and after checking whether the AG device supports
+ * this feature or not by invoking {@link BluetoothDevice#isRequestAudioPolicyAsSinkSupported}.
+ * Only after getting a {@link BluetoothStatusCodes#FEATURE_SUPPORTED} response
+ * from the API should the APIs related to this feature be used.
+ *
+ *
+ * @hide
+ */
+public final class BluetoothSinkAudioPolicy implements Parcelable {
+
+ /**
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ prefix = {"POLICY_"},
+ value = {
+ POLICY_UNCONFIGURED,
+ POLICY_ALLOWED,
+ POLICY_NOT_ALLOWED,
+ }
+ )
+ public @interface AudioPolicyValues{}
+
+ /**
+ * Audio behavior not configured for the policy.
+ *
+ * If a policy is set with this value, it means that the policy is not
+ * configured with a value yet and should not be used to make any decision.
+ * @hide
+ */
+ public static final int POLICY_UNCONFIGURED = 0;
+
+ /**
+ * Audio is preferred by HF device for the policy.
+ *
+ * If a policy is set with this value, then the HF device will prefer the
+ * audio for the policy use case. For example, if the Call Establish audio
+ * policy is set with this value, then the HF will prefer the audio
+ * during making or picking up a call.
+ * @hide
+ */
+ public static final int POLICY_ALLOWED = 1;
+
+ /**
+ * Audio is not preferred by HF device for the policy.
+ *
+ * If a policy is set with this value, then the HF device will not prefer the
+ * audio for the policy use case. For example, if the Call Establish audio
+ * policy is set with this value, then the HF will not prefer the audio
+ * during making or picking up a call.
+ * @hide
+ */
+ public static final int POLICY_NOT_ALLOWED = 2;
+
+ @AudioPolicyValues private final int mCallEstablishPolicy;
+ @AudioPolicyValues private final int mConnectingTimePolicy;
+ @AudioPolicyValues private final int mInBandRingtonePolicy;
+
+ /**
+ * @hide
+ */
+ public BluetoothSinkAudioPolicy(int callEstablishPolicy,
+ int connectingTimePolicy, int inBandRingtonePolicy) {
+ mCallEstablishPolicy = callEstablishPolicy;
+ mConnectingTimePolicy = connectingTimePolicy;
+ mInBandRingtonePolicy = inBandRingtonePolicy;
+ }
+
+ /**
+ * Get Call establishment policy audio policy.
+ * <p>This policy is used to determine the audio preference when the
+ * HF device makes or answers a call. That is, if this device
+ * makes or answers a call, is the audio preferred by HF.
+ *
+ * @return the call pick up audio policy value
+ *
+ * @hide
+ */
+ public @AudioPolicyValues int getCallEstablishPolicy() {
+ return mCallEstablishPolicy;
+ }
+
+ /**
+ * Get during connection audio up policy.
+ * <p>This policy is used to determine the audio preference when the
+ * HF device connects with the AG device. That is, when the
+ * HF device gets connected, should the HF become active and get audio
+ * is decided by this policy. This also covers the case of during a call.
+ * If the HF connects with the AG during an ongoing call, should the call
+ * audio be routed to the HF will be decided by this policy.
+ *
+ * @return the during connection audio policy value
+ *
+ * @hide
+ */
+ public @AudioPolicyValues int getActiveDevicePolicyAfterConnection() {
+ return mConnectingTimePolicy;
+ }
+
+ /**
+ * Get In band ringtone audio up policy.
+ * <p>This policy is used to determine the audio preference of the
+ * in band ringtone. That is, if there is an incoming call, should the
+ * inband ringtone audio be routed to the HF will be decided by this policy.
+ *
+ * @return the in band ringtone audio policy value
+ *
+ * @hide
+ */
+ public @AudioPolicyValues int getInBandRingtonePolicy() {
+ return mInBandRingtonePolicy;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder("BluetoothSinkAudioPolicy{");
+ builder.append("mCallEstablishPolicy: ");
+ builder.append(mCallEstablishPolicy);
+ builder.append(", mConnectingTimePolicy: ");
+ builder.append(mConnectingTimePolicy);
+ builder.append(", mInBandRingtonePolicy: ");
+ builder.append(mInBandRingtonePolicy);
+ builder.append("}");
+ return builder.toString();
+ }
+
+ /**
+ * {@link Parcelable.Creator} interface implementation.
+ */
+ public static final @android.annotation.NonNull Parcelable.Creator<BluetoothSinkAudioPolicy>
+ CREATOR = new Parcelable.Creator<BluetoothSinkAudioPolicy>() {
+ @Override
+ public BluetoothSinkAudioPolicy createFromParcel(@NonNull Parcel in) {
+ return new BluetoothSinkAudioPolicy(
+ in.readInt(), in.readInt(), in.readInt());
+ }
+
+ @Override
+ public BluetoothSinkAudioPolicy[] newArray(int size) {
+ return new BluetoothSinkAudioPolicy[size];
+ }
+ };
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeInt(mCallEstablishPolicy);
+ out.writeInt(mConnectingTimePolicy);
+ out.writeInt(mInBandRingtonePolicy);
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (o instanceof BluetoothSinkAudioPolicy) {
+ BluetoothSinkAudioPolicy other = (BluetoothSinkAudioPolicy) o;
+ return (other.mCallEstablishPolicy == mCallEstablishPolicy
+ && other.mConnectingTimePolicy == mConnectingTimePolicy
+ && other.mInBandRingtonePolicy == mInBandRingtonePolicy);
+ }
+ return false;
+ }
+
+ /**
+ * Returns a hash representation of this BluetoothCodecConfig
+ * based on all the config values.
+ *
+ * @hide
+ */
+ @Override
+ public int hashCode() {
+ return Objects.hash(mCallEstablishPolicy, mConnectingTimePolicy, mInBandRingtonePolicy);
+ }
+
+ /**
+ * Builder for {@link BluetoothSinkAudioPolicy}.
+ * <p> By default, the audio policy values will be set to
+ * {@link BluetoothSinkAudioPolicy#POLICY_UNCONFIGURED}.
+ */
+ public static final class Builder {
+ private int mCallEstablishPolicy = POLICY_UNCONFIGURED;
+ private int mConnectingTimePolicy = POLICY_UNCONFIGURED;
+ private int mInBandRingtonePolicy = POLICY_UNCONFIGURED;
+
+ public Builder() {
+
+ }
+
+ public Builder(@NonNull BluetoothSinkAudioPolicy policies) {
+ mCallEstablishPolicy = policies.mCallEstablishPolicy;
+ mConnectingTimePolicy = policies.mConnectingTimePolicy;
+ mInBandRingtonePolicy = policies.mInBandRingtonePolicy;
+ }
+
+ /**
+ * Set Call Establish (pick up and answer) policy.
+ * <p>This policy is used to determine the audio preference when the
+ * HF device makes or answers a call. That is, if this device
+ * makes or answers a call, is the audio preferred by HF.
+ * <p>If set to {@link BluetoothSinkAudioPolicy#POLICY_ALLOWED}, answering or making
+ * a call from the HF device will route the call audio to it.
+ * If set to {@link BluetoothSinkAudioPolicy#POLICY_NOT_ALLOWED}, answering or making
+ * a call from the HF device will NOT route the call audio to it.
+ *
+ * @return reference to the current object
+ *
+ * @hide
+ */
+ public @NonNull Builder setCallEstablishPolicy(
+ @AudioPolicyValues int callEstablishPolicy) {
+ mCallEstablishPolicy = callEstablishPolicy;
+ return this;
+ }
+
+ /**
+ * Set during connection audio up policy.
+ * <p>This policy is used to determine the audio preference when the
+ * HF device connects with the AG device. That is, when the
+ * HF device gets connected, should the HF become active and get audio
+ * is decided by this policy. This also covers the case of during a call.
+ * If the HF connects with the AG during an ongoing call, should the call
+ * audio be routed to the HF will be decided by this policy.
+ * <p>If set to {@link BluetoothSinkAudioPolicy#POLICY_ALLOWED}, connecting HF
+ * during a call will route the call audio to it.
+ * If set to {@link BluetoothSinkAudioPolicy#POLICY_NOT_ALLOWED}, connecting HF
+ * during a call will NOT route the call audio to it.
+ *
+ * @return reference to the current object
+ *
+ * @hide
+ */
+ public @NonNull Builder setActiveDevicePolicyAfterConnection(
+ @AudioPolicyValues int connectingTimePolicy) {
+ mConnectingTimePolicy = connectingTimePolicy;
+ return this;
+ }
+
+ /**
+ * Set In band ringtone audio up policy.
+ * <p>This policy is used to determine the audio preference of the
+ * in band ringtone. That is, if there is an incoming call, should the
+ * inband ringtone audio be routed to the HF will be decided by this policy.
+ * <p>If set to {@link BluetoothSinkAudioPolicy#POLICY_ALLOWED}, there will be
+ * in band ringtone in the HF device during an incoming call.
+ * If set to {@link BluetoothSinkAudioPolicy#POLICY_NOT_ALLOWED}, there will NOT
+ * be in band ringtone in the HF device during an incoming call.
+ *
+ * @return reference to the current object
+ *
+ * @hide
+ */
+ public @NonNull Builder setInBandRingtonePolicy(
+ @AudioPolicyValues int inBandRingtonePolicy) {
+ mInBandRingtonePolicy = inBandRingtonePolicy;
+ return this;
+ }
+
+ /**
+ * Build {@link BluetoothSinkAudioPolicy}.
+ * @return new BluetoothSinkAudioPolicy object
+ *
+ * @hide
+ */
+ public @NonNull BluetoothSinkAudioPolicy build() {
+ return new BluetoothSinkAudioPolicy(
+ mCallEstablishPolicy, mConnectingTimePolicy, mInBandRingtonePolicy);
+ }
+ }
+}
diff --git a/framework/java/android/bluetooth/BluetoothSocket.java b/framework/java/android/bluetooth/BluetoothSocket.java
index ef2f7b8..b57bfca 100644
--- a/framework/java/android/bluetooth/BluetoothSocket.java
+++ b/framework/java/android/bluetooth/BluetoothSocket.java
@@ -285,7 +285,7 @@
BluetoothSocket as = new BluetoothSocket(this);
as.mSocketState = SocketState.CONNECTED;
FileDescriptor[] fds = mSocket.getAncillaryFileDescriptors();
- if (DBG) Log.d(TAG, "socket fd passed by stack fds: " + Arrays.toString(fds));
+ if (DBG) Log.d(TAG, "acceptSocket: socket fd passed by stack fds:" + Arrays.toString(fds));
if (fds == null || fds.length != 1) {
Log.e(TAG, "socket fd passed from stack failed, fds: " + Arrays.toString(fds));
as.close();
@@ -450,6 +450,7 @@
throw new IOException("bt socket closed");
}
mSocketState = SocketState.CONNECTED;
+ if (DBG) Log.d(TAG, "connect(), socket connected");
}
} catch (RemoteException e) {
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
@@ -536,8 +537,8 @@
if (mSocketState != SocketState.LISTENING) {
throw new IOException("bt socket is not in listen state");
}
+ Log.d(TAG, "accept(), timeout (ms):" + timeout);
if (timeout > 0) {
- Log.d(TAG, "accept() set timeout (ms):" + timeout);
mSocket.setSoTimeout(timeout);
}
String RemoteAddr = waitSocketSignal(mSocketIS);
diff --git a/framework/java/android/bluetooth/BluetoothStatusCodes.java b/framework/java/android/bluetooth/BluetoothStatusCodes.java
index 35e03f5..24be7fc 100644
--- a/framework/java/android/bluetooth/BluetoothStatusCodes.java
+++ b/framework/java/android/bluetooth/BluetoothStatusCodes.java
@@ -216,6 +216,12 @@
public static final int ERROR_REMOTE_OPERATION_NOT_SUPPORTED = 27;
/**
+ * Indicates that the feature status is not configured yet.
+ * @hide
+ */
+ public static final int FEATURE_NOT_CONFIGURED = 30;
+
+ /**
* A GATT writeCharacteristic request is not permitted on the remote device.
*/
public static final int ERROR_GATT_WRITE_NOT_ALLOWED = 200;
diff --git a/framework/tests/unit/Android.bp b/framework/tests/unit/Android.bp
index 23d9db2..a976d96 100644
--- a/framework/tests/unit/Android.bp
+++ b/framework/tests/unit/Android.bp
@@ -30,5 +30,4 @@
"general-tests",
"mts-bluetooth",
],
- certificate: ":com.android.bluetooth.certificate",
}
diff --git a/framework/tests/unit/AndroidTest.xml b/framework/tests/unit/AndroidTest.xml
index f6dfc2d..4c04e11 100644
--- a/framework/tests/unit/AndroidTest.xml
+++ b/framework/tests/unit/AndroidTest.xml
@@ -19,6 +19,10 @@
<option name="test-file-name" value="FrameworkBluetoothTests.apk" />
</target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
+ <option name="force-root" value="true" />
+ </target_preparer>
+
<option name="test-suite-tag" value="apct"/>
<option name="test-tag" value="FrameworkBluetoothTests"/>
diff --git a/pandora/interfaces/pandora_experimental/hfp.proto b/pandora/interfaces/pandora_experimental/hfp.proto
index 496e665..2406165 100644
--- a/pandora/interfaces/pandora_experimental/hfp.proto
+++ b/pandora/interfaces/pandora_experimental/hfp.proto
@@ -31,6 +31,28 @@
rpc SetVoiceRecognition(SetVoiceRecognitionRequest) returns (SetVoiceRecognitionResponse);
// Clear the call history
rpc ClearCallHistory(ClearCallHistoryRequest) returns (ClearCallHistoryResponse);
+ // Answer an incoming call from a peer device (as a handsfree)
+ rpc AnswerCallAsHandsfree(AnswerCallAsHandsfreeRequest) returns (AnswerCallAsHandsfreeResponse);
+ // End a call from a peer device (as a handsfree)
+ rpc EndCallAsHandsfree(EndCallAsHandsfreeRequest) returns (EndCallAsHandsfreeResponse);
+ // Decline an incoming call from a peer device (as a handsfree)
+ rpc DeclineCallAsHandsfree(DeclineCallAsHandsfreeRequest) returns (DeclineCallAsHandsfreeResponse);
+ // Connect to an incoming audio stream from a peer device (as a handsfree)
+ rpc ConnectToAudioAsHandsfree(ConnectToAudioAsHandsfreeRequest) returns (ConnectToAudioAsHandsfreeResponse);
+ // Disonnect from an incoming audio stream from a peer device (as a handsfree)
+ rpc DisconnectFromAudioAsHandsfree(DisconnectFromAudioAsHandsfreeRequest) returns (DisconnectFromAudioAsHandsfreeResponse);
+ // Make a call to a given phone number (as a handsfree)
+ rpc MakeCallAsHandsfree(MakeCallAsHandsfreeRequest) returns (MakeCallAsHandsfreeResponse);
+ // Connect a call on hold, and disconnect the current call (as a handsfree)
+ rpc CallTransferAsHandsfree(CallTransferAsHandsfreeRequest) returns (CallTransferAsHandsfreeResponse);
+ // Enable Service level connection (as a handsfree)
+ rpc EnableSlcAsHandsfree(EnableSlcAsHandsfreeRequest) returns (google.protobuf.Empty);
+ // Disable Service level connection (as a handsfree)
+ rpc DisableSlcAsHandsfree(DisableSlcAsHandsfreeRequest) returns (google.protobuf.Empty);
+ // Set voice recognition (as a handsfree)
+ rpc SetVoiceRecognitionAsHandsfree(SetVoiceRecognitionAsHandsfreeRequest) returns (SetVoiceRecognitionAsHandsfreeResponse);
+ // Send DTMF code from the handsfree
+ rpc SendDtmfFromHandsfree(SendDtmfFromHandsfreeRequest) returns (SendDtmfFromHandsfreeResponse);
}
// Request of the `EnableSlc` method.
@@ -99,3 +121,68 @@
message ClearCallHistoryRequest {}
message ClearCallHistoryResponse {}
+
+message AnswerCallAsHandsfreeRequest {
+ Connection connection = 1;
+}
+
+message AnswerCallAsHandsfreeResponse {}
+
+message EndCallAsHandsfreeRequest {
+ Connection connection = 1;
+}
+
+message EndCallAsHandsfreeResponse {}
+
+message DeclineCallAsHandsfreeRequest {
+ Connection connection = 1;
+}
+
+message DeclineCallAsHandsfreeResponse {}
+
+message ConnectToAudioAsHandsfreeRequest {
+ Connection connection = 1;
+}
+
+message ConnectToAudioAsHandsfreeResponse {}
+
+message DisconnectFromAudioAsHandsfreeRequest {
+ Connection connection = 1;
+}
+
+message DisconnectFromAudioAsHandsfreeResponse {}
+
+message MakeCallAsHandsfreeRequest {
+ Connection connection = 1;
+ string number = 2;
+}
+
+message MakeCallAsHandsfreeResponse {}
+
+message CallTransferAsHandsfreeRequest {
+ Connection connection = 1;
+}
+
+message CallTransferAsHandsfreeResponse {}
+
+message EnableSlcAsHandsfreeRequest {
+ Connection connection = 1;
+}
+
+message DisableSlcAsHandsfreeRequest {
+ Connection connection = 1;
+}
+
+message SetVoiceRecognitionAsHandsfreeRequest {
+ Connection connection = 1;
+ bool enabled = 2;
+}
+
+message SetVoiceRecognitionAsHandsfreeResponse {}
+
+message SendDtmfFromHandsfreeRequest {
+ Connection connection = 1;
+ uint32 code = 2;
+}
+
+message SendDtmfFromHandsfreeResponse {}
diff --git a/service/java/com/android/server/bluetooth/BluetoothManagerService.java b/service/java/com/android/server/bluetooth/BluetoothManagerService.java
index da4b71e..994b438 100644
--- a/service/java/com/android/server/bluetooth/BluetoothManagerService.java
+++ b/service/java/com/android/server/bluetooth/BluetoothManagerService.java
@@ -302,6 +302,8 @@
// Save a ProfileServiceConnections object for each of the bound
// bluetooth profile services
private final Map<Integer, ProfileServiceConnections> mProfileServices = new HashMap<>();
+ @GuardedBy("mProfileServices")
+ private boolean mUnbindingAll = false;
private final IBluetoothCallback mBluetoothCallback = new IBluetoothCallback.Stub() {
@Override
@@ -333,10 +335,7 @@
@VisibleForTesting
public void onInitFlagsChanged() {
- mHandler.removeMessages(MESSAGE_INIT_FLAGS_CHANGED);
- mHandler.sendEmptyMessageDelayed(
- MESSAGE_INIT_FLAGS_CHANGED,
- DELAY_BEFORE_RESTART_DUE_TO_INIT_FLAGS_CHANGED_MS);
+ // TODO(b/265386284)
}
public boolean onFactoryReset(AttributionSource attributionSource) {
@@ -969,7 +968,7 @@
}
public int getState() {
- if ((Binder.getCallingUid() != Process.SYSTEM_UID) && (!checkIfCallerIsForegroundUser())) {
+ if (!isCallerSystem(getCallingAppId()) && !checkIfCallerIsForegroundUser()) {
Log.w(TAG, "getState(): report OFF for non-active and non system user");
return BluetoothAdapter.STATE_OFF;
}
@@ -1146,11 +1145,11 @@
}
return false;
}
- // Check if packageName belongs to callingUid
- final int callingUid = Binder.getCallingUid();
- final boolean isCallerSystem = UserHandle.getAppId(callingUid) == Process.SYSTEM_UID;
- if (!isCallerSystem && callingUid != Process.SHELL_UID) {
- checkPackage(callingUid, attributionSource.getPackageName());
+ int callingAppId = getCallingAppId();
+ if (!isCallerSystem(callingAppId)
+ && !isCallerShell(callingAppId)
+ && !isCallerRoot(callingAppId)) {
+ checkPackage(attributionSource.getPackageName());
if (requireForeground && !checkIfCallerIsForegroundUser()) {
Log.w(TAG, "Not allowed for non-active and non system user");
@@ -1456,24 +1455,27 @@
}
/**
- * Check if AppOpsManager is available and the packageName belongs to uid
+ * Check if AppOpsManager is available and the packageName belongs to calling uid
*
* A null package belongs to any uid
*/
- private void checkPackage(int uid, String packageName) {
+ private void checkPackage(String packageName) {
+ int callingUid = Binder.getCallingUid();
+
if (mAppOps == null) {
Log.w(TAG, "checkPackage(): called before system boot up, uid "
- + uid + ", packageName " + packageName);
+ + callingUid + ", packageName " + packageName);
throw new IllegalStateException("System has not boot yet");
}
if (packageName == null) {
- Log.w(TAG, "checkPackage(): called with null packageName from " + uid);
+ Log.w(TAG, "checkPackage(): called with null packageName from " + callingUid);
return;
}
+
try {
- mAppOps.checkPackage(uid, packageName);
+ mAppOps.checkPackage(callingUid, packageName);
} catch (SecurityException e) {
- Log.w(TAG, "checkPackage(): " + packageName + " does not belong to uid " + uid);
+ Log.w(TAG, "checkPackage(): " + packageName + " does not belong to uid " + callingUid);
throw new SecurityException(e.getMessage());
}
}
@@ -1595,13 +1597,16 @@
} catch (IllegalArgumentException e) {
Log.e(TAG, "Unable to unbind service with intent: " + psc.mIntent, e);
}
- mProfileServices.remove(profile);
+ if (!mUnbindingAll) {
+ mProfileServices.remove(profile);
+ }
}
}
}
private void unbindAllBluetoothProfileServices() {
synchronized (mProfileServices) {
+ mUnbindingAll = true;
for (Integer i : mProfileServices.keySet()) {
ProfileServiceConnections psc = mProfileServices.get(i);
try {
@@ -1611,6 +1616,7 @@
}
psc.removeAllProxies();
}
+ mUnbindingAll = false;
mProfileServices.clear();
}
}
@@ -1909,7 +1915,7 @@
return null;
}
- if ((Binder.getCallingUid() != Process.SYSTEM_UID) && (!checkIfCallerIsForegroundUser())) {
+ if (!isCallerSystem(getCallingAppId()) && !checkIfCallerIsForegroundUser()) {
Log.w(TAG, "getAddress(): not allowed for non-active and non system user");
return null;
}
@@ -1943,7 +1949,7 @@
return null;
}
- if ((Binder.getCallingUid() != Process.SYSTEM_UID) && (!checkIfCallerIsForegroundUser())) {
+ if (!isCallerSystem(getCallingAppId()) && !checkIfCallerIsForegroundUser()) {
Log.w(TAG, "getName(): not allowed for non-active and non system user");
return null;
}
@@ -2715,6 +2721,19 @@
}
}
+ private static int getCallingAppId() {
+ return UserHandle.getAppId(Binder.getCallingUid());
+ }
+ private static boolean isCallerSystem(int callingAppId) {
+ return callingAppId == Process.SYSTEM_UID;
+ }
+ private static boolean isCallerShell(int callingAppId) {
+ return callingAppId == Process.SHELL_UID;
+ }
+ private static boolean isCallerRoot(int callingAppId) {
+ return callingAppId == Process.ROOT_UID;
+ }
+
private boolean checkIfCallerIsForegroundUser() {
int callingUid = Binder.getCallingUid();
UserHandle callingUser = UserHandle.getUserHandleForUid(callingUid);
@@ -2832,11 +2851,18 @@
sendBluetoothStateCallback(isUp);
sendBleStateChanged(prevState, newState);
- } else if (newState == BluetoothAdapter.STATE_BLE_TURNING_ON
- || newState == BluetoothAdapter.STATE_BLE_TURNING_OFF) {
+ } else if (newState == BluetoothAdapter.STATE_BLE_TURNING_ON) {
sendBleStateChanged(prevState, newState);
isStandardBroadcast = false;
-
+ } else if (newState == BluetoothAdapter.STATE_BLE_TURNING_OFF) {
+ sendBleStateChanged(prevState, newState);
+ if (prevState != BluetoothAdapter.STATE_TURNING_OFF) {
+ isStandardBroadcast = false;
+ } else {
+ // Broadcast as STATE_OFF for app that do not receive BLE update
+ newState = BluetoothAdapter.STATE_OFF;
+ sendBrEdrDownCallback(mContext.getAttributionSource());
+ }
} else if (newState == BluetoothAdapter.STATE_TURNING_ON
|| newState == BluetoothAdapter.STATE_TURNING_OFF) {
sendBleStateChanged(prevState, newState);
diff --git a/sysprop/Android.bp b/sysprop/Android.bp
new file mode 100644
index 0000000..67cc6eb
--- /dev/null
+++ b/sysprop/Android.bp
@@ -0,0 +1,20 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+sysprop_library {
+ name: "com.android.sysprop.bluetooth",
+ host_supported: true,
+ srcs: [
+ "avrcp.sysprop",
+ "bta.sysprop",
+ "hfp.sysprop",
+ "pan.sysprop",
+ ],
+ property_owner: "Platform",
+ api_packages: ["android.sysprop"],
+ cpp: {
+ min_sdk_version: "Tiramisu",
+ },
+ apex_available: ["com.android.btservices"],
+}
diff --git a/sysprop/OWNERS b/sysprop/OWNERS
new file mode 100644
index 0000000..263e053
--- /dev/null
+++ b/sysprop/OWNERS
@@ -0,0 +1,2 @@
+licorne@google.com
+wescande@google.com
diff --git a/sysprop/avrcp.sysprop b/sysprop/avrcp.sysprop
new file mode 100644
index 0000000..6c12087
--- /dev/null
+++ b/sysprop/avrcp.sysprop
@@ -0,0 +1,11 @@
+module: "android.sysprop.bluetooth.Avrcp"
+owner: Platform
+
+prop {
+ api_name: "absolute_volume"
+ type: Boolean
+ scope: Internal
+ access: Readonly
+ prop_name: "bluetooth.avrcp.absolute_volume.enabled"
+}
+
diff --git a/sysprop/bta.sysprop b/sysprop/bta.sysprop
new file mode 100644
index 0000000..a438a10
--- /dev/null
+++ b/sysprop/bta.sysprop
@@ -0,0 +1,12 @@
+module: "android.sysprop.bluetooth.Bta"
+owner: Platform
+
+prop {
+ api_name: "disable_delay"
+ type: Integer
+ scope: Internal
+ access: Readonly
+ prop_name: "bluetooth.bta.disable_delay.millis"
+}
+
+
diff --git a/sysprop/hfp.sysprop b/sysprop/hfp.sysprop
new file mode 100644
index 0000000..f07124f
--- /dev/null
+++ b/sysprop/hfp.sysprop
@@ -0,0 +1,35 @@
+module: "android.sysprop.bluetooth.Hfp"
+owner: Platform
+
+prop {
+ api_name: "hf_client_features"
+ type: Integer
+ scope: Internal
+ access: Readonly
+ prop_name: "bluetooth.hfp.hf_client_features.config"
+}
+
+prop {
+ api_name: "hf_features"
+ type: Integer
+ scope: Internal
+ access: Readonly
+ prop_name: "bluetooth.hfp.hf_features.config"
+}
+
+prop {
+ api_name: "hf_services"
+ type: Integer
+ scope: Internal
+ access: Readonly
+ prop_name: "bluetooth.hfp.hf_services_mask.config"
+}
+
+prop {
+ api_name: "version"
+ type: Integer
+ scope: Internal
+ access: Readonly
+ prop_name: "bluetooth.hfp.version.config"
+}
+
diff --git a/sysprop/pan.sysprop b/sysprop/pan.sysprop
new file mode 100644
index 0000000..fa64bb3
--- /dev/null
+++ b/sysprop/pan.sysprop
@@ -0,0 +1,10 @@
+module: "android.sysprop.bluetooth.Pan"
+owner: Platform
+
+prop {
+ api_name: "nap"
+ type: Boolean
+ scope: Internal
+ access: Readonly
+ prop_name: "bluetooth.pan.nap.enabled"
+}
diff --git a/system/audio_hal_interface/aidl/client_interface_aidl.cc b/system/audio_hal_interface/aidl/client_interface_aidl.cc
index efdc469..9af2803 100644
--- a/system/audio_hal_interface/aidl/client_interface_aidl.cc
+++ b/system/audio_hal_interface/aidl/client_interface_aidl.cc
@@ -55,9 +55,8 @@
}
bool BluetoothAudioClientInterface::is_aidl_available() {
- auto service = AServiceManager_checkService(
+ return AServiceManager_isDeclared(
kDefaultAudioProviderFactoryInterface.c_str());
- return (service != nullptr);
}
std::vector<AudioCapabilities>
@@ -72,7 +71,7 @@
return capabilities;
}
auto provider_factory = IBluetoothAudioProviderFactory::fromBinder(
- ::ndk::SpAIBinder(AServiceManager_getService(
+ ::ndk::SpAIBinder(AServiceManager_waitForService(
kDefaultAudioProviderFactoryInterface.c_str())));
if (provider_factory == nullptr) {
@@ -91,16 +90,15 @@
}
void BluetoothAudioClientInterface::FetchAudioProvider() {
- if (provider_ != nullptr) {
- LOG(WARNING) << __func__ << ": refetch";
- } else if (!is_aidl_available()) {
- // AIDL availability should only be checked at the beginning.
- // When refetching, AIDL may not be ready *yet* but it's expected to be
- // available later.
+ if (!is_aidl_available()) {
+ LOG(ERROR) << __func__ << ": aidl is not supported on this platform.";
return;
}
+ if (provider_ != nullptr) {
+ LOG(WARNING) << __func__ << ": refetch";
+ }
auto provider_factory = IBluetoothAudioProviderFactory::fromBinder(
- ::ndk::SpAIBinder(AServiceManager_getService(
+ ::ndk::SpAIBinder(AServiceManager_waitForService(
kDefaultAudioProviderFactoryInterface.c_str())));
if (provider_factory == nullptr) {
diff --git a/system/binder/android/bluetooth/BluetoothSinkAudioPolicy.aidl b/system/binder/android/bluetooth/BluetoothSinkAudioPolicy.aidl
new file mode 100644
index 0000000..740249c
--- /dev/null
+++ b/system/binder/android/bluetooth/BluetoothSinkAudioPolicy.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth;
+
+parcelable BluetoothSinkAudioPolicy;
diff --git a/system/binder/android/bluetooth/IBluetooth.aidl b/system/binder/android/bluetooth/IBluetooth.aidl
index 68237c6..360220c 100644
--- a/system/binder/android/bluetooth/IBluetooth.aidl
+++ b/system/binder/android/bluetooth/IBluetooth.aidl
@@ -25,6 +25,7 @@
import android.bluetooth.IBluetoothSocketManager;
import android.bluetooth.IBluetoothStateChangeCallback;
import android.bluetooth.BluetoothActivityEnergyInfo;
+import android.bluetooth.BluetoothSinkAudioPolicy;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.IncomingRfcommSocketInfo;
@@ -267,6 +268,13 @@
oneway void allowLowLatencyAudio(in boolean allowed, in BluetoothDevice device, in SynchronousResultReceiver receiver);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
+ void isRequestAudioPolicyAsSinkSupported(in BluetoothDevice device, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
+ void requestAudioPolicyAsSink(in BluetoothDevice device, in BluetoothSinkAudioPolicy policies, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
+ void getRequestedAudioPolicyAsSink(in BluetoothDevice device, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
+
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
oneway void startRfcommListener(String name, in ParcelUuid uuid, in PendingIntent intent, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
oneway void stopRfcommListener(in ParcelUuid uuid, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
diff --git a/system/binder/android/bluetooth/IBluetoothHeadsetClient.aidl b/system/binder/android/bluetooth/IBluetoothHeadsetClient.aidl
index 3e1e83b..ad3316c 100644
--- a/system/binder/android/bluetooth/IBluetoothHeadsetClient.aidl
+++ b/system/binder/android/bluetooth/IBluetoothHeadsetClient.aidl
@@ -17,6 +17,7 @@
package android.bluetooth;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothSinkAudioPolicy;
import android.bluetooth.BluetoothHeadsetClientCall;
import android.content.AttributionSource;
@@ -86,6 +87,7 @@
void setAudioRouteAllowed(in BluetoothDevice device, boolean allowed, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
void getAudioRouteAllowed(in BluetoothDevice device, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
+
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
void sendVendorAtCommand(in BluetoothDevice device, int vendorId, String atCommand, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
diff --git a/system/blueberry/tests/sl4a_sl4a/l2cap/le_l2cap_coc_test.py b/system/blueberry/tests/sl4a_sl4a/l2cap/le_l2cap_coc_test.py
new file mode 100644
index 0000000..ee94901
--- /dev/null
+++ b/system/blueberry/tests/sl4a_sl4a/l2cap/le_l2cap_coc_test.py
@@ -0,0 +1,169 @@
+#!/usr/bin/env python3
+#
+# Copyright 2022 - The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import binascii
+import io
+import logging
+import os
+import queue
+import time
+
+from blueberry.tests.gd.cert.context import get_current_context
+from blueberry.tests.gd.cert.truth import assertThat
+from blueberry.tests.gd_sl4a.lib.bt_constants import ble_address_types
+from blueberry.tests.sl4a_sl4a.lib import sl4a_sl4a_base_test
+from blueberry.tests.sl4a_sl4a.lib.security import Security
+
+
+class LeL2capCoCTest(sl4a_sl4a_base_test.Sl4aSl4aBaseTestClass):
+
+ def __get_cert_public_address_and_irk_from_bt_config(self):
+ # Pull IRK from SL4A cert side to pass in from SL4A DUT side when scanning
+ bt_config_file_path = os.path.join(get_current_context().get_full_output_path(),
+ "DUT_%s_bt_config.conf" % self.cert.serial)
+ try:
+ self.cert.adb.pull(["/data/misc/bluedroid/bt_config.conf", bt_config_file_path])
+ except AdbError as error:
+ logging.error("Failed to pull SL4A cert BT config")
+ return False
+ logging.debug("Reading SL4A cert BT config")
+ with io.open(bt_config_file_path) as f:
+ for line in f.readlines():
+ stripped_line = line.strip()
+ if (stripped_line.startswith("Address")):
+ address_fields = stripped_line.split(' ')
+ # API currently requires public address to be capitalized
+ address = address_fields[2].upper()
+ logging.debug("Found cert address: %s" % address)
+ continue
+ if (stripped_line.startswith("LE_LOCAL_KEY_IRK")):
+ irk_fields = stripped_line.split(' ')
+ irk = irk_fields[2]
+ logging.debug("Found cert IRK: %s" % irk)
+ continue
+
+ return address, irk
+
+ def setup_class(self):
+ super().setup_class()
+
+ def setup_test(self):
+ assertThat(super().setup_test()).isTrue()
+
+ def teardown_test(self):
+ self.dut_scanner_.stop_scanning()
+ self.cert_advertiser_.stop_advertising()
+ self.dut_security_.remove_all_bonded_devices()
+ self.cert_security_.remove_all_bonded_devices()
+ super().teardown_test()
+
+ # Scans for the cert device by name. We expect to get back a RPA.
+ def __scan_for_cert_by_name(self):
+ cert_public_address, irk = self.__get_cert_public_address_and_irk_from_bt_config()
+ self.cert_advertiser_.advertise_public_extended_pdu()
+ advertising_name = self.cert_advertiser_.get_local_advertising_name()
+
+ # Scan with name and verify we get back a scan result with the RPA
+ scan_result_addr = self.dut_scanner_.scan_for_name(advertising_name)
+ assertThat(scan_result_addr).isNotNone()
+ assertThat(scan_result_addr).isNotEqualTo(cert_public_address)
+
+ return scan_result_addr
+
+ def __scan_for_irk(self):
+ cert_public_address, irk = self.__get_cert_public_address_and_irk_from_bt_config()
+ rpa_address = self.cert_advertiser_.advertise_public_extended_pdu()
+ id_addr = self.dut_scanner_.scan_for_address_with_irk(cert_public_address, ble_address_types["public"], irk)
+ self.dut_scanner_.stop_scanning()
+ return id_addr
+
+ def __create_le_bond_oob_single_sided(self,
+ wait_for_oob_data=True,
+ wait_for_device_bonded=True,
+ addr=None,
+ addr_type=ble_address_types["random"]):
+ oob_data = self.cert_security_.generate_oob_data(Security.TRANSPORT_LE, wait_for_oob_data)
+ if wait_for_oob_data:
+ assertThat(oob_data[0]).isEqualTo(0)
+ assertThat(oob_data[1]).isNotNone()
+ self.dut_security_.create_bond_out_of_band(oob_data[1], addr, addr_type, wait_for_device_bonded)
+ return oob_data[1].to_sl4a_address()
+
+ def __create_le_bond_oob_double_sided(self,
+ wait_for_oob_data=True,
+ wait_for_device_bonded=True,
+ addr=None,
+ addr_type=ble_address_types["random"]):
+ # Genearte OOB data on DUT, but we don't use it
+ self.dut_security_.generate_oob_data(Security.TRANSPORT_LE, wait_for_oob_data)
+ self.__create_le_bond_oob_single_sided(wait_for_oob_data, wait_for_device_bonded, addr, addr_type)
+
+ def __test_le_l2cap_insecure_coc(self):
+ logging.info("Testing insecure L2CAP CoC")
+ cert_rpa = self.__scan_for_cert_by_name()
+
+ # Listen on an insecure l2cap coc on the cert
+ psm = self.cert_l2cap_.listen_using_l2cap_le_coc(False)
+ self.dut_l2cap_.create_l2cap_le_coc(cert_rpa, psm, False)
+
+ # Cleanup
+ self.dut_scanner_.stop_scanning()
+ self.dut_l2cap_.close_l2cap_le_coc_client()
+ self.cert_advertiser_.stop_advertising()
+ self.cert_l2cap_.close_l2cap_le_coc_server()
+
+ def __test_le_l2cap_secure_coc(self):
+ logging.info("Testing secure L2CAP CoC")
+ cert_rpa = self.__create_le_bond_oob_single_sided()
+
+ # Listen on an secure l2cap coc on the cert
+ psm = self.cert_l2cap_.listen_using_l2cap_le_coc(True)
+ self.dut_l2cap_.create_l2cap_le_coc(cert_rpa, psm, True)
+
+ # Cleanup
+ self.dut_scanner_.stop_scanning()
+ self.dut_l2cap_.close_l2cap_le_coc_client()
+ self.cert_advertiser_.stop_advertising()
+ self.cert_l2cap_.close_l2cap_le_coc_server()
+ self.dut_security_.remove_all_bonded_devices()
+ self.cert_security_.remove_all_bonded_devices()
+
+ def __test_le_l2cap_secure_coc_after_irk_scan(self):
+ logging.info("Testing secure L2CAP CoC after IRK scan")
+ cert_config_addr, irk = self.__get_cert_public_address_and_irk_from_bt_config()
+ cert_id_addr = self.__scan_for_irk()
+
+ assertThat(cert_id_addr).isEqualTo(cert_config_addr)
+ self.__create_le_bond_oob_single_sided(True, True, cert_id_addr, ble_address_types["public"])
+ self.cert_advertiser_.stop_advertising()
+ self.__test_le_l2cap_secure_coc()
+
+ def __test_secure_le_l2cap_coc_stress(self):
+ for i in range(0, 10):
+ self.__test_le_l2cap_secure_coc()
+
+ def __test_insecure_le_l2cap_coc_stress(self):
+ for i in range(0, 10):
+ self.__test_le_l2cap_insecure_coc()
+
+ def __test_le_l2cap_coc_stress(self):
+ #for i in range (0, 10):
+ self.__test_le_l2cap_insecure_coc()
+ self.__test_le_l2cap_secure_coc()
+
+ def __test_secure_le_l2cap_coc_after_irk_scan_stress(self):
+ for i in range(0, 10):
+ self.__test_le_l2cap_secure_coc_after_irk_scan()
diff --git a/system/blueberry/tests/sl4a_sl4a/lib/l2cap.py b/system/blueberry/tests/sl4a_sl4a/lib/l2cap.py
index 3957146..9604ab7 100644
--- a/system/blueberry/tests/sl4a_sl4a/lib/l2cap.py
+++ b/system/blueberry/tests/sl4a_sl4a/lib/l2cap.py
@@ -15,24 +15,56 @@
# limitations under the License.
import logging
+import queue
from blueberry.tests.gd.cert.truth import assertThat
class L2cap:
- __l2cap_connection_timeout = 30 #seconds
+ __l2cap_connection_timeout = 10 #seconds
__device = None
+ __active_client_coc = False
+ __active_server_coc = False
def __init__(self, device):
self.__device = device
+ def __wait_for_event(self, expected_event_name):
+ try:
+ event_info = self.__device.ed.pop_event(expected_event_name, self.__l2cap_connection_timeout)
+ logging.info(event_info)
+ except queue.Empty as error:
+ logging.error("Failed to find event: %s", expected_event_name)
+ return False
+ return True
+
def create_l2cap_le_coc(self, address, psm, secure):
logging.info("creating l2cap channel with secure=%r and psm %s", secure, psm)
self.__device.sl4a.bluetoothSocketConnBeginConnectThreadPsm(address, True, psm, secure)
+ assertThat(self.__wait_for_event("BluetoothSocketConnectSuccess")).isTrue()
+ self.__active_client_coc = True
# Starts listening on the l2cap server socket, returns the psm
- def listen_using_l2cap_coc(self, secure):
- logging.info("Listening for l2cap channel with secure=%r and psm %s", secure, psm)
- self.__device.sl4a.bluetoothSocketConnBeginAcceptThreadPsm(__l2cap_connection_timeout, True, secure)
+ def listen_using_l2cap_le_coc(self, secure):
+ logging.info("Listening for l2cap channel with secure=%r", secure)
+ self.__device.sl4a.bluetoothSocketConnBeginAcceptThreadPsm(self.__l2cap_connection_timeout, True, secure)
+ self.__active_server_coc = True
return self.__device.sl4a.bluetoothSocketConnGetPsm()
+
+ def close_l2cap_le_coc_client(self):
+ if self.__active_client_coc:
+ logging.info("Closing LE L2CAP CoC Client")
+ self.__device.sl4a.bluetoothSocketConnKillConnThread()
+ self.__active_client_coc = False
+
+ def close_l2cap_le_coc_server(self):
+ if self.__active_server_coc:
+ logging.info("Closing LE L2CAP CoC Server")
+ self.__device.sl4a.bluetoothSocketConnEndAcceptThread()
+ self.__active_server_coc = False
+
+ def close(self):
+ self.close_l2cap_le_coc_client()
+ self.close_l2cap_le_coc_server()
+ self.__device == None
diff --git a/system/blueberry/tests/sl4a_sl4a/lib/oob_data.py b/system/blueberry/tests/sl4a_sl4a/lib/oob_data.py
index 69f753a..e54c467 100644
--- a/system/blueberry/tests/sl4a_sl4a/lib/oob_data.py
+++ b/system/blueberry/tests/sl4a_sl4a/lib/oob_data.py
@@ -49,4 +49,4 @@
def to_sl4a_address_type(self):
if len(self.address) != self.ADDRESS_WITH_TYPE_LENGTH:
return -1
- return self.address.upper()[-2]
+ return self.address.upper()[-1]
diff --git a/system/blueberry/tests/sl4a_sl4a/lib/security.py b/system/blueberry/tests/sl4a_sl4a/lib/security.py
index fcfb4ba..78cc758 100644
--- a/system/blueberry/tests/sl4a_sl4a/lib/security.py
+++ b/system/blueberry/tests/sl4a_sl4a/lib/security.py
@@ -37,7 +37,7 @@
TRANSPORT_LE = "2"
__default_timeout = 10 # seconds
- __default_bonding_timeout = 30 # seconds
+ __default_bonding_timeout = 60 # seconds
__device = None
def __init__(self, device):
@@ -70,6 +70,7 @@
logging.info("Generating local oob data failed with error code %d", errorcode)
return errorcode, None
+ logging.info("OOB ADDR with Type: %s", generate_success_event["data"]["address_with_type"])
return 0, OobData(generate_success_event["data"]["address_with_type"],
generate_success_event["data"]["confirmation"], generate_success_event["data"]["randomizer"])
@@ -84,13 +85,27 @@
logging.info("Bonded: %s", bond_state["data"]["bonded_state"])
assertThat(bond_state["data"]["bonded_state"]).isEqualTo(True)
- def create_bond_out_of_band(self, oob_data, wait_for_device_bonded=True):
+ def create_bond_out_of_band(self,
+ oob_data,
+ bt_device_object_address=None,
+ bt_device_object_address_type=-1,
+ wait_for_device_bonded=True):
assertThat(oob_data).isNotNone()
- address = oob_data.to_sl4a_address()
- address_type = oob_data.to_sl4a_address_type()
- logging.info("Bonding OOB with %s and address type=%s", address, address_type)
- self.__device.sl4a.bluetoothCreateLeBondOutOfBand(address, oob_data.confirmation, oob_data.randomizer,
- address_type)
+ oob_data_address = oob_data.to_sl4a_address()
+ oob_data_address_type = oob_data.to_sl4a_address_type()
+
+ # If a BT Device object address isn't specified, default to the oob data
+ # address and type
+ if bt_device_object_address is None:
+ bt_device_object_address = oob_data_address
+ bt_device_object_address_type = oob_data_address_type
+
+ logging.info("Bonding OOB with device addr=%s, device addr type=%s, oob addr=%s, oob addr type=%s",
+ bt_device_object_address, bt_device_object_address_type, oob_data_address, oob_data_address_type)
+ bond_start = self.__device.sl4a.bluetoothCreateLeBondOutOfBand(
+ oob_data_address, oob_data_address_type, oob_data.confirmation, oob_data.randomizer,
+ bt_device_object_address, bt_device_object_address_type)
+ assertThat(bond_start).isTrue()
if wait_for_device_bonded:
self.ensure_device_bonded()
diff --git a/system/blueberry/tests/sl4a_sl4a/lib/sl4a_sl4a_base_test.py b/system/blueberry/tests/sl4a_sl4a/lib/sl4a_sl4a_base_test.py
index fe37b00..0184288 100644
--- a/system/blueberry/tests/sl4a_sl4a/lib/sl4a_sl4a_base_test.py
+++ b/system/blueberry/tests/sl4a_sl4a/lib/sl4a_sl4a_base_test.py
@@ -26,6 +26,7 @@
from blueberry.tests.gd_sl4a.lib.ble_lib import enable_bluetooth
from blueberry.tests.sl4a_sl4a.lib.le_advertiser import LeAdvertiser
from blueberry.tests.sl4a_sl4a.lib.le_scanner import LeScanner
+from blueberry.tests.sl4a_sl4a.lib.l2cap import L2cap
from blueberry.tests.sl4a_sl4a.lib.security import Security
from blueberry.utils.mobly_sl4a_utils import setup_sl4a
from blueberry.utils.mobly_sl4a_utils import teardown_sl4a
@@ -43,11 +44,13 @@
dut_advertiser_ = None
dut_scanner_ = None
dut_security_ = None
+ dut_l2cap_ = None
# CERT
cert_advertiser_ = None
cert_scanner_ = None
cert_security_ = None
+ cert_l2cap_ = None
SUBPROCESS_WAIT_TIMEOUT_SECONDS = 10
@@ -111,9 +114,11 @@
self.dut_advertiser_ = LeAdvertiser(self.dut)
self.dut_scanner_ = LeScanner(self.dut)
self.dut_security_ = Security(self.dut)
+ self.dut_l2cap_ = L2cap(self.dut)
self.cert_advertiser_ = LeAdvertiser(self.cert)
self.cert_scanner_ = LeScanner(self.cert)
self.cert_security_ = Security(self.cert)
+ self.cert_l2cap_ = L2cap(self.cert)
return True
def teardown_test(self):
@@ -121,13 +126,16 @@
safeClose(self.dut_advertiser_)
safeClose(self.dut_scanner_)
safeClose(self.dut_security_)
+ safeClose(self.dut_l2cap_)
safeClose(self.cert_advertiser_)
safeClose(self.cert_scanner_)
safeClose(self.cert_security_)
+ safeClose(self.cert_l2cap_)
self.dut_advertiser_ = None
self.dut_scanner_ = None
self.dut_security_ = None
self.cert_advertiser_ = None
+ self.cert_l2cap_ = None
self.cert_scanner_ = None
self.cert_security_ = None
diff --git a/system/blueberry/tests/sl4a_sl4a/sl4a_sl4a_test_runner.py b/system/blueberry/tests/sl4a_sl4a/sl4a_sl4a_test_runner.py
index 7b2b933..3081aa4 100644
--- a/system/blueberry/tests/sl4a_sl4a/sl4a_sl4a_test_runner.py
+++ b/system/blueberry/tests/sl4a_sl4a/sl4a_sl4a_test_runner.py
@@ -18,6 +18,7 @@
from blueberry.tests.sl4a_sl4a.gatt.gatt_connect_test import GattConnectTest
from blueberry.tests.sl4a_sl4a.gatt.gatt_connect_with_irk_test import GattConnectWithIrkTest
from blueberry.tests.sl4a_sl4a.gatt.gatt_notify_test import GattNotifyTest
+from blueberry.tests.sl4a_sl4a.l2cap.le_l2cap_coc_test import LeL2capCoCTest
from blueberry.tests.sl4a_sl4a.scanning.le_scanning import LeScanningTest
from blueberry.tests.sl4a_sl4a.security.irk_rotation_test import IrkRotationTest
from blueberry.tests.sl4a_sl4a.security.oob_pairing_test import OobPairingTest
@@ -31,6 +32,7 @@
GattNotifyTest,
IrkRotationTest,
LeAdvertisingTest,
+ LeL2capCoCTest,
LeScanningTest,
OobPairingTest,
]
diff --git a/system/bta/Android.bp b/system/bta/Android.bp
index e22f8b8..513599e 100644
--- a/system/bta/Android.bp
+++ b/system/bta/Android.bp
@@ -126,6 +126,7 @@
"hh/bta_hh_le.cc",
"hh/bta_hh_main.cc",
"hh/bta_hh_utils.cc",
+ "hfp/bta_hfp_api.cc",
"hd/bta_hd_act.cc",
"hd/bta_hd_api.cc",
"hd/bta_hd_main.cc",
@@ -147,6 +148,7 @@
static_libs: [
"avrcp-target-service",
"lib-bt-packets",
+ "libcom.android.sysprop.bluetooth",
],
generated_headers: [
"LeAudioSetConfigSchemas_h",
@@ -187,6 +189,7 @@
"libbt-audio-hal-interface",
"libbluetooth-types",
"libbt-protos-lite",
+ "libcom.android.sysprop.bluetooth",
"libosi",
"libbt-common",
],
@@ -297,6 +300,7 @@
"libbt-common",
"libbt-protos-lite",
"libbtcore",
+ "libcom.android.sysprop.bluetooth",
"libflatbuffers-cpp",
"libgmock",
],
@@ -326,6 +330,7 @@
"packages/modules/Bluetooth/system/utils/include",
],
srcs: [
+ "hf_client/bta_hf_client_api.cc",
"test/bta_hf_client_add_record_test.cc",
],
header_libs: ["libbluetooth_headers"],
@@ -335,6 +340,7 @@
],
static_libs: [
"libbluetooth-types",
+ "libcom.android.sysprop.bluetooth",
"libosi",
],
cflags: ["-DBUILDCFG"],
@@ -889,6 +895,7 @@
"test/common/btm_api_mock.cc",
"test/common/mock_controller.cc",
"test/common/mock_csis_client.cc",
+ ":TestStubOsi",
],
shared_libs: [
"libprotobuf-cpp-lite",
diff --git a/system/bta/BUILD.gn b/system/bta/BUILD.gn
index db8db54..22cf22e 100644
--- a/system/bta/BUILD.gn
+++ b/system/bta/BUILD.gn
@@ -74,6 +74,7 @@
"hh/bta_hh_le.cc",
"hh/bta_hh_main.cc",
"hh/bta_hh_utils.cc",
+ "hfp/bta_hfp_api.cc",
"hd/bta_hd_act.cc",
"hd/bta_hd_api.cc",
"hd/bta_hd_main.cc",
diff --git a/system/bta/ag/bta_ag_sdp.cc b/system/bta/ag/bta_ag_sdp.cc
index 46e5825..27503b1 100644
--- a/system/bta/ag/bta_ag_sdp.cc
+++ b/system/bta/ag/bta_ag_sdp.cc
@@ -161,7 +161,7 @@
/* add profile descriptor list */
if (service_uuid == UUID_SERVCLASS_AG_HANDSFREE) {
profile_uuid = UUID_SERVCLASS_HF_HANDSFREE;
- version = BTA_HFP_VERSION;
+ version = get_default_hfp_version();
} else {
profile_uuid = UUID_SERVCLASS_HEADSET;
version = HSP_VERSION_1_2;
diff --git a/system/bta/av/bta_av_cfg.cc b/system/bta/av/bta_av_cfg.cc
index d2a7d8a..bebd05e 100644
--- a/system/bta/av/bta_av_cfg.cc
+++ b/system/bta/av/bta_av_cfg.cc
@@ -92,16 +92,16 @@
(sizeof(bta_av_meta_caps_evt_ids) / sizeof(bta_av_meta_caps_evt_ids[0]))
#endif /* BTA_AV_NUM_RC_EVT_IDS */
-const uint8_t bta_avk_meta_caps_evt_ids[] = {
-#if (AVRC_ADV_CTRL_INCLUDED == TRUE)
- AVRC_EVT_VOLUME_CHANGE,
-#endif
-};
-
-#ifndef BTA_AVK_NUM_RC_EVT_IDS
-#define BTA_AVK_NUM_RC_EVT_IDS \
- (sizeof(bta_avk_meta_caps_evt_ids) / sizeof(bta_avk_meta_caps_evt_ids[0]))
-#endif /* BTA_AVK_NUM_RC_EVT_IDS */
+const uint8_t* get_bta_avk_meta_caps_evt_ids() {
+ if (avrcp_absolute_volume_is_enabled()) {
+ static const uint8_t bta_avk_meta_caps_evt_ids[] = {
+ AVRC_EVT_VOLUME_CHANGE,
+ };
+ return bta_avk_meta_caps_evt_ids;
+ } else {
+ return {};
+ }
+}
// These are the only events used with AVRCP1.3
const uint8_t bta_av_meta_caps_evt_ids_avrcp13[] = {
@@ -113,7 +113,7 @@
#define BTA_AV_NUM_RC_EVT_IDS_AVRCP13 \
(sizeof(bta_av_meta_caps_evt_ids_avrcp13) / \
sizeof(bta_av_meta_caps_evt_ids_avrcp13[0]))
-#endif /* BTA_AVK_NUM_RC_EVT_IDS_AVRCP13 */
+#endif /* BTA_AV_NUM_RC_EVT_IDS_AVRCP13 */
/* This configuration to be used when we are Src + TG + CT( only for abs vol) */
extern const tBTA_AV_CFG bta_av_cfg = {
@@ -136,23 +136,29 @@
/* This configuration to be used when we are Sink + CT + TG( only for abs vol)
*/
-extern const tBTA_AV_CFG bta_avk_cfg = {
- AVRC_CO_METADATA, /* AVRCP Company ID */
- BTA_AVK_RC_SUPF_CT, /* AVRCP controller categories */
- BTA_AVK_RC_SUPF_TG, /* AVRCP target categories */
- 6, /* AVDTP audio channel max data queue size */
- false, /* true, to accept AVRC 1.3 group nevigation command */
- 2, /* company id count in p_meta_co_ids */
- BTA_AVK_NUM_RC_EVT_IDS, /* event id count in p_meta_evt_ids */
- BTA_AV_RC_PASS_RSP_CODE, /* the default response code for pass
- through commands */
- bta_av_meta_caps_co_ids, /* the metadata Get Capabilities response
- for company id */
- bta_avk_meta_caps_evt_ids, /* the the metadata Get Capabilities
- response for event id */
- {0}, /* Default AVRCP controller name */
- {0}, /* Default AVRCP target name */
-};
+
+const tBTA_AV_CFG* get_bta_avk_cfg() {
+ static const tBTA_AV_CFG bta_avk_cfg = {
+ AVRC_CO_METADATA, /* AVRCP Company ID */
+ BTA_AVK_RC_SUPF_CT, /* AVRCP controller categories */
+ BTA_AVK_RC_SUPF_TG, /* AVRCP target categories */
+ 6, /* AVDTP audio channel max data queue size */
+ false, /* true, to accept AVRC 1.3 group nevigation command */
+ 2, /* company id count in p_meta_co_ids */
+ (uint8_t)(avrcp_absolute_volume_is_enabled()
+ ? 1
+ : 0), /* event id count in p_meta_evt_ids */
+ BTA_AV_RC_PASS_RSP_CODE, /* the default response code for pass
+ through commands */
+ bta_av_meta_caps_co_ids, /* the metadata Get Capabilities response
+ for company id */
+ get_bta_avk_meta_caps_evt_ids(), /* the metadata Get Capabilities response
+ for event id */
+ {0}, /* Default AVRCP controller name */
+ {0}, /* Default AVRCP target name */
+ };
+ return &bta_avk_cfg;
+}
/* This configuration to be used when we are using AVRCP1.3 */
extern const tBTA_AV_CFG bta_av_cfg_compatibility = {
diff --git a/system/bta/av/bta_av_int.h b/system/bta/av/bta_av_int.h
index 94e2f5e..f50f5bc 100644
--- a/system/bta/av/bta_av_int.h
+++ b/system/bta/av/bta_av_int.h
@@ -690,7 +690,7 @@
/* config struct */
extern const tBTA_AV_CFG* p_bta_av_cfg;
-extern const tBTA_AV_CFG bta_avk_cfg;
+const tBTA_AV_CFG* get_bta_avk_cfg();
extern const tBTA_AV_CFG bta_av_cfg;
extern const tBTA_AV_CFG bta_av_cfg_compatibility;
diff --git a/system/bta/av/bta_av_main.cc b/system/bta/av/bta_av_main.cc
index b4f9e8e..dfbd90d 100644
--- a/system/bta/av/bta_av_main.cc
+++ b/system/bta/av/bta_av_main.cc
@@ -432,7 +432,7 @@
uint16_t profile_initialized = p_data->api_reg.service_uuid;
if (profile_initialized == UUID_SERVCLASS_AUDIO_SINK) {
- p_bta_av_cfg = &bta_avk_cfg;
+ p_bta_av_cfg = get_bta_avk_cfg();
} else if (profile_initialized == UUID_SERVCLASS_AUDIO_SOURCE) {
p_bta_av_cfg = &bta_av_cfg;
diff --git a/system/bta/csis/csis_client.cc b/system/bta/csis/csis_client.cc
index a1a1f45..d24c02c 100644
--- a/system/bta/csis/csis_client.cc
+++ b/system/bta/csis/csis_client.cc
@@ -338,11 +338,16 @@
}
/* In case of GATT ERROR */
- LOG(ERROR) << __func__ << " Incorrect write status "
- << loghex((int)(status));
+ LOG_ERROR("Incorrect write status=0x%02x", (int)(status));
/* Unlock previous devices */
HandleCsisLockProcedureError(csis_group, device);
+
+ if (status == GATT_DATABASE_OUT_OF_SYNC) {
+ LOG_INFO("Database out of sync for %s",
+ device->addr.ToString().c_str());
+ ClearDeviceInformationAndStartSearch(device);
+ }
return;
}
@@ -839,6 +844,11 @@
BtaGattQueue::Clean(conn_id);
return;
}
+
+ if (status == GATT_DATABASE_OUT_OF_SYNC) {
+ LOG_INFO("Database out of sync for %s", device->addr.ToString().c_str());
+ ClearDeviceInformationAndStartSearch(device);
+ }
}
void OnCsisNotification(uint16_t conn_id, uint16_t handle, uint16_t len,
@@ -955,13 +965,17 @@
return;
}
- DLOG(INFO) << __func__ << " " << device->addr
- << " status: " << loghex(+status);
+ LOG_DEBUG("%s, status: 0x%02x", device->addr.ToString().c_str(), status);
if (status != GATT_SUCCESS) {
- LOG(ERROR) << __func__ << " Could not read characteristic at handle="
- << loghex(handle);
- BTA_GATTC_Close(device->conn_id);
+ if (status == GATT_DATABASE_OUT_OF_SYNC) {
+ LOG_INFO("Database out of sync for %s",
+ device->addr.ToString().c_str());
+ ClearDeviceInformationAndStartSearch(device);
+ } else {
+ LOG_ERROR("Could not read characteristic at handle=0x%04x", handle);
+ BTA_GATTC_Close(device->conn_id);
+ }
return;
}
@@ -999,13 +1013,17 @@
return;
}
- LOG(INFO) << __func__ << " " << device->addr
- << " status: " << loghex(+status);
+ LOG_INFO("%s, status 0x%02x", device->addr.ToString().c_str(), status);
if (status != GATT_SUCCESS) {
- LOG(ERROR) << __func__ << " Could not read characteristic at handle="
- << loghex(handle);
- BTA_GATTC_Close(device->conn_id);
+ if (status == GATT_DATABASE_OUT_OF_SYNC) {
+ LOG_INFO("Database out of sync for %s",
+ device->addr.ToString().c_str());
+ ClearDeviceInformationAndStartSearch(device);
+ } else {
+ LOG_ERROR("Could not read characteristic at handle=0x%04x", handle);
+ BTA_GATTC_Close(device->conn_id);
+ }
return;
}
@@ -1034,13 +1052,18 @@
return;
}
- DLOG(INFO) << __func__ << " " << device->addr
- << " status: " << loghex(+status) << " rank:" << int(value[0]);
+ LOG_DEBUG("%s, status: 0x%02x, rank: %d", device->addr.ToString().c_str(),
+ status, value[0]);
if (status != GATT_SUCCESS) {
- LOG(ERROR) << __func__ << " Could not read characteristic at handle="
- << loghex(handle);
- BTA_GATTC_Close(device->conn_id);
+ if (status == GATT_DATABASE_OUT_OF_SYNC) {
+ LOG_INFO("Database out of sync for %s",
+ device->addr.ToString().c_str());
+ ClearDeviceInformationAndStartSearch(device);
+ } else {
+ LOG_ERROR("Could not read characteristic at handle=0x%04x", handle);
+ BTA_GATTC_Close(device->conn_id);
+ }
return;
}
@@ -1131,14 +1154,14 @@
std::vector<RawAddress> GetAllRsiFromAdvertising(
const tBTA_DM_INQ_RES* result) {
const uint8_t* p_service_data = result->p_eir;
- uint16_t remaining_data_len = result->eir_len;
std::vector<RawAddress> devices;
uint8_t service_data_len = 0;
while ((p_service_data = AdvertiseDataParser::GetFieldByType(
p_service_data + service_data_len,
- (remaining_data_len -= service_data_len), BTM_BLE_AD_TYPE_RSI,
- &service_data_len))) {
+ result->eir_len - (p_service_data - result->p_eir) -
+ service_data_len,
+ BTM_BLE_AD_TYPE_RSI, &service_data_len))) {
RawAddress bda;
STREAM_TO_BDADDR(bda, p_service_data);
devices.push_back(std::move(bda));
@@ -1333,17 +1356,21 @@
return;
}
- DLOG(INFO) << __func__ << " " << device->addr
- << " status: " << loghex(+status);
+ LOG_DEBUG("%s, status: 0x%02x", device->addr.ToString().c_str(), status);
if (status != GATT_SUCCESS) {
/* TODO handle error codes:
* kCsisErrorCodeLockAccessSirkRejected
* kCsisErrorCodeLockOobSirkOnly
*/
- LOG(ERROR) << __func__ << " Could not read characteristic at handle="
- << loghex(handle);
- BTA_GATTC_Close(device->conn_id);
+ if (status == GATT_DATABASE_OUT_OF_SYNC) {
+ LOG_INFO("Database out of sync for %s",
+ device->addr.ToString().c_str());
+ ClearDeviceInformationAndStartSearch(device);
+ } else {
+ LOG_ERROR("Could not read characteristic at handle=0x%04x", handle);
+ BTA_GATTC_Close(device->conn_id);
+ }
return;
}
@@ -1438,9 +1465,7 @@
CsisActiveDiscovery(csis_group);
}
- void DoDisconnectCleanUp(std::shared_ptr<CsisDevice> device) {
- DLOG(INFO) << __func__ << ": device=" << device->addr;
-
+ void DeregisterNotifications(std::shared_ptr<CsisDevice> device) {
device->ForEachCsisInstance(
[&](const std::shared_ptr<CsisInstance>& csis_inst) {
DisableGattNotification(device->conn_id, device->addr,
@@ -1450,6 +1475,12 @@
DisableGattNotification(device->conn_id, device->addr,
csis_inst->svc_data.size_handle.val_hdl);
});
+ }
+
+ void DoDisconnectCleanUp(std::shared_ptr<CsisDevice> device) {
+ LOG_INFO("%s", device->addr.ToString().c_str());
+
+ DeregisterNotifications(device);
if (device->IsConnected()) {
BtaGattQueue::Clean(device->conn_id);
@@ -1845,6 +1876,22 @@
}
}
+ void ClearDeviceInformationAndStartSearch(
+ std::shared_ptr<CsisDevice> device) {
+ LOG_INFO("%s ", device->addr.ToString().c_str());
+ if (device->is_gatt_service_valid == false) {
+ LOG_DEBUG("Device database already invalidated.");
+ return;
+ }
+
+ /* Invalidate service discovery results */
+ BtaGattQueue::Clean(device->conn_id);
+ device->first_connection = true;
+ DeregisterNotifications(device);
+ device->ClearSvcData();
+ BTA_GATTC_ServiceSearchRequest(device->conn_id, &kCsisServiceUuid);
+ }
+
void OnGattServiceChangeEvent(const RawAddress& address) {
auto device = FindDeviceByAddress(address);
if (!device) {
@@ -1852,12 +1899,8 @@
return;
}
- DLOG(INFO) << __func__ << ": address=" << address;
-
- /* Invalidate service discovery results */
- BtaGattQueue::Clean(device->conn_id);
- device->first_connection = true;
- device->ClearSvcData();
+ LOG_INFO("%s", address.ToString().c_str());
+ ClearDeviceInformationAndStartSearch(device);
}
void OnGattServiceDiscoveryDoneEvent(const RawAddress& address) {
diff --git a/system/bta/csis/csis_client_test.cc b/system/bta/csis/csis_client_test.cc
index 6cac395..b7ca1db 100644
--- a/system/bta/csis/csis_client_test.cc
+++ b/system/bta/csis/csis_client_test.cc
@@ -1124,6 +1124,42 @@
TestAppUnregister();
}
+TEST_F(CsisClientTest, test_database_out_of_sync) {
+ auto test_address = GetTestAddress(0);
+ auto conn_id = 1;
+
+ TestAppRegister();
+ SetSampleDatabaseCsis(conn_id, 1);
+ TestConnect(test_address);
+ InjectConnectedEvent(test_address, conn_id);
+ GetSearchCompleteEvent(conn_id);
+ ASSERT_EQ(1, CsisClient::Get()->GetGroupId(
+ test_address, bluetooth::Uuid::From16Bit(0x0000)));
+
+ // Simulated database changed on the remote side.
+ ON_CALL(gatt_queue, WriteCharacteristic(_, _, _, _, _, _))
+ .WillByDefault(
+ Invoke([this](uint16_t conn_id, uint16_t handle,
+ std::vector<uint8_t> value, tGATT_WRITE_TYPE write_type,
+ GATT_WRITE_OP_CB cb, void* cb_data) {
+ auto* svc = gatt::FindService(services_map[conn_id], handle);
+ if (svc == nullptr) return;
+
+ tGATT_STATUS status = GATT_DATABASE_OUT_OF_SYNC;
+ if (cb)
+ cb(conn_id, status, handle, value.size(), value.data(), cb_data);
+ }));
+
+ ON_CALL(gatt_interface, ServiceSearchRequest(_, _)).WillByDefault(Return());
+ EXPECT_CALL(gatt_interface, ServiceSearchRequest(_, _));
+ CsisClient::Get()->LockGroup(
+ 1, true,
+ base::BindOnce([](int group_id, bool locked, CsisGroupLockStatus status) {
+ csis_lock_callback_mock->CsisGroupLockCb(group_id, locked, status);
+ }));
+ TestAppUnregister();
+}
+
} // namespace
} // namespace internal
} // namespace csis
diff --git a/system/bta/dm/bta_dm_act.cc b/system/bta/dm/bta_dm_act.cc
index 1896cc9..711014b 100644
--- a/system/bta/dm/bta_dm_act.cc
+++ b/system/bta/dm/bta_dm_act.cc
@@ -26,6 +26,9 @@
#define LOG_TAG "bt_bta_dm"
#include <base/logging.h>
+#ifdef OS_ANDROID
+#include <bta.sysprop.h>
+#endif
#include <cstdint>
@@ -157,16 +160,18 @@
#define BTA_DM_SWITCH_DELAY_TIMER_MS 500
#endif
-namespace {
-
// Time to wait after receiving shutdown request to delay the actual shutdown
// process. This time may be zero which invokes immediate shutdown.
-#ifndef BTA_DISABLE_DELAY
-constexpr uint64_t kDisableDelayTimerInMs = 0;
+static uint64_t get_DisableDelayTimerInMs() {
+#ifndef OS_ANDROID
+ return 200;
#else
-constexpr uint64_t kDisableDelayTimerInMs =
- static_cast<uint64_t>(BTA_DISABLE_DELAY);
+ static const uint64_t kDisableDelayTimerInMs =
+ android::sysprop::bluetooth::Bta::disable_delay().value_or(200);
+ return kDisableDelayTimerInMs;
#endif
+}
+namespace {
struct WaitForAllAclConnectionsToDrain {
uint64_t time_to_wait_in_ms;
@@ -353,8 +358,10 @@
* graceful shutdown.
*/
bta_dm_search_cb.search_timer = alarm_new("bta_dm_search.search_timer");
+ bool delay_close_gatt =
+ osi_property_get_bool("bluetooth.gatt.delay_close.enabled", true);
bta_dm_search_cb.gatt_close_timer =
- alarm_new("bta_dm_search.gatt_close_timer");
+ delay_close_gatt ? alarm_new("bta_dm_search.gatt_close_timer") : nullptr;
bta_dm_search_cb.pending_discovery_queue = fixed_queue_new(SIZE_MAX);
memset(&bta_dm_conn_srvcs, 0, sizeof(bta_dm_conn_srvcs));
@@ -448,15 +455,15 @@
if (BTM_GetNumAclLinks() == 0) {
// We can shut down faster if there are no ACL links
- switch (kDisableDelayTimerInMs) {
+ switch (get_DisableDelayTimerInMs()) {
case 0:
LOG_DEBUG("Immediately disabling device manager");
bta_dm_disable_conn_down_timer_cback(nullptr);
break;
default:
LOG_DEBUG("Set timer to delay disable initiation:%lu ms",
- static_cast<unsigned long>(kDisableDelayTimerInMs));
- alarm_set_on_mloop(bta_dm_cb.disable_timer, kDisableDelayTimerInMs,
+ static_cast<unsigned long>(get_DisableDelayTimerInMs()));
+ alarm_set_on_mloop(bta_dm_cb.disable_timer, get_DisableDelayTimerInMs(),
bta_dm_disable_conn_down_timer_cback, nullptr);
}
} else {
@@ -1861,6 +1868,8 @@
result.inq_res.p_eir = const_cast<uint8_t*>(p_eir);
result.inq_res.eir_len = eir_len;
+ result.inq_res.ble_evt_type = p_inq->ble_evt_type;
+
p_inq_info = BTM_InqDbRead(p_inq->remote_bd_addr);
if (p_inq_info != NULL) {
/* initialize remt_name_not_required to false so that we get the name by
@@ -2545,9 +2554,6 @@
memset(&bta_dm_cb.device_list.peer_device[clear_index], 0,
sizeof(bta_dm_cb.device_list.peer_device[clear_index]));
}
-
- device->conn_state = BTA_DM_NOT_CONNECTED;
-
break;
}
if (bta_dm_cb.device_list.count) bta_dm_cb.device_list.count--;
@@ -4002,11 +4008,25 @@
bta_sys_sendmsg(p_msg);
if (conn_id != GATT_INVALID_CONN_ID) {
- /* start a GATT channel close delay timer */
- bta_sys_start_timer(bta_dm_search_cb.gatt_close_timer,
- BTA_DM_GATT_CLOSE_DELAY_TOUT,
- BTA_DM_DISC_CLOSE_TOUT_EVT, 0);
bta_dm_search_cb.pending_close_bda = bta_dm_search_cb.peer_bdaddr;
+ // Gatt will be close immediately if bluetooth.gatt.delay_close.enabled is
+ // set to false. If property is true / unset there will be a delay
+ if (bta_dm_search_cb.gatt_close_timer != nullptr) {
+ /* start a GATT channel close delay timer */
+ bta_sys_start_timer(bta_dm_search_cb.gatt_close_timer,
+ BTA_DM_GATT_CLOSE_DELAY_TOUT,
+ BTA_DM_DISC_CLOSE_TOUT_EVT, 0);
+ } else {
+ p_msg = (tBTA_DM_MSG*)osi_malloc(sizeof(tBTA_DM_MSG));
+ p_msg->hdr.event = BTA_DM_DISC_CLOSE_TOUT_EVT;
+ p_msg->hdr.layer_specific = 0;
+ bta_sys_sendmsg(p_msg);
+ }
+ } else {
+ if (bluetooth::common::init_flags::
+ bta_dm_clear_conn_id_on_client_close_is_enabled()) {
+ bta_dm_search_cb.conn_id = GATT_INVALID_CONN_ID;
+ }
}
bta_dm_search_cb.gatt_disc_active = false;
}
@@ -4139,7 +4159,8 @@
break;
case BTA_GATTC_CLOSE_EVT:
- LOG_DEBUG("BTA_GATTC_CLOSE_EVT reason = %d", p_data->close.reason);
+ LOG_INFO("BTA_GATTC_CLOSE_EVT reason = %d", p_data->close.reason);
+
/* in case of disconnect before search is completed */
if ((bta_dm_search_cb.state != BTA_DM_SEARCH_IDLE) &&
(bta_dm_search_cb.state != BTA_DM_SEARCH_ACTIVE) &&
diff --git a/system/bta/dm/bta_dm_main.cc b/system/bta/dm/bta_dm_main.cc
index e844790..59bf00c 100644
--- a/system/bta/dm/bta_dm_main.cc
+++ b/system/bta/dm/bta_dm_main.cc
@@ -61,8 +61,8 @@
*
******************************************************************************/
bool bta_dm_search_sm_execute(BT_HDR_RIGID* p_msg) {
- APPL_TRACE_EVENT("bta_dm_search_sm_execute state:%d, event:0x%x",
- bta_dm_search_cb.state, p_msg->event);
+ LOG_INFO("bta_dm_search_sm_execute state:%d, event:0x%x",
+ bta_dm_search_get_state(), p_msg->event);
tBTA_DM_MSG* message = (tBTA_DM_MSG*)p_msg;
switch (bta_dm_search_cb.state) {
@@ -123,6 +123,16 @@
bta_dm_search_cancel_notify();
bta_dm_execute_queued_request();
break;
+ case BTA_DM_DISC_CLOSE_TOUT_EVT:
+ if (bluetooth::common::init_flags::
+ bta_dm_clear_conn_id_on_client_close_is_enabled()) {
+ bta_dm_close_gatt_conn(message);
+ break;
+ }
+ [[fallthrough]];
+ default:
+ LOG_INFO("Received unexpected event 0x%x in state %d", p_msg->event,
+ bta_dm_search_cb.state);
}
break;
case BTA_DM_DISCOVER_ACTIVE:
@@ -145,6 +155,16 @@
case BTA_DM_API_DISCOVER_EVT:
bta_dm_queue_disc(message);
break;
+ case BTA_DM_DISC_CLOSE_TOUT_EVT:
+ if (bluetooth::common::init_flags::
+ bta_dm_clear_conn_id_on_client_close_is_enabled()) {
+ bta_dm_close_gatt_conn(message);
+ break;
+ }
+ [[fallthrough]];
+ default:
+ LOG_INFO("Received unexpected event 0x%x in state %d", p_msg->event,
+ bta_dm_search_cb.state);
}
break;
}
diff --git a/system/bta/gatt/bta_gattc_act.cc b/system/bta/gatt/bta_gattc_act.cc
index 48aa7b9..c9c791a 100644
--- a/system/bta/gatt/bta_gattc_act.cc
+++ b/system/bta/gatt/bta_gattc_act.cc
@@ -34,6 +34,7 @@
#include "bta/hh/bta_hh_int.h"
#include "btif/include/btif_debug_conn.h"
#include "device/include/controller.h"
+#include "device/include/interop.h"
#include "main/shim/dumpsys.h"
#include "osi/include/allocator.h"
#include "osi/include/log.h"
@@ -595,6 +596,7 @@
cb_data.close.conn_id = p_data->hdr.layer_specific;
cb_data.close.remote_bda = p_clcb->bda;
cb_data.close.reason = BTA_GATT_CONN_NONE;
+ cb_data.close.status = GATT_ERROR;
LOG(WARNING) << __func__ << ": conn_id=" << loghex(cb_data.close.conn_id)
<< ". Returns GATT_ERROR(" << +GATT_ERROR << ").";
@@ -645,7 +647,7 @@
bta_gattc_clcb_dealloc(p_clcb);
if (p_data->hdr.event == BTA_GATTC_API_CLOSE_EVT) {
- GATT_Disconnect(p_data->hdr.layer_specific);
+ cb_data.close.status = GATT_Disconnect(p_data->hdr.layer_specific);
cb_data.close.reason = GATT_CONN_TERMINATE_LOCAL_HOST;
LOG_DEBUG("Local close event client_if:%hu conn_id:%hu reason:%s",
cb_data.close.client_if, cb_data.close.conn_id,
@@ -653,6 +655,7 @@
static_cast<tGATT_DISCONN_REASON>(cb_data.close.reason))
.c_str());
} else if (p_data->hdr.event == BTA_GATTC_INT_DISCONN_EVT) {
+ cb_data.close.status = static_cast<tGATT_STATUS>(p_data->int_conn.reason);
cb_data.close.reason = p_data->int_conn.reason;
LOG_DEBUG("Peer close disconnect event client_if:%hu conn_id:%hu reason:%s",
cb_data.close.client_if, cb_data.close.conn_id,
@@ -802,6 +805,18 @@
p_clcb->p_srcb->srvc_hdl_db_hash = false;
}
+ // Some LMP 5.2 devices also don't support robust caching. This workaround
+ // conditionally disables the feature based on a combination of LMP
+ // version and OUI prefix.
+ if (lmp_version < 0x0c &&
+ interop_match_addr(INTEROP_DISABLE_ROBUST_CACHING, &p_clcb->bda)) {
+ LOG_WARN(
+ "Device LMP version 0x%02x <= Bluetooth 5.2 and MAC addr on "
+ "interop list, skipping robust caching",
+ lmp_version);
+ p_clcb->p_srcb->srvc_hdl_db_hash = false;
+ }
+
/* read db hash if db hash characteristic exists */
if (bta_gattc_is_robust_caching_enabled() &&
p_clcb->p_srcb->srvc_hdl_db_hash &&
diff --git a/system/bta/gatt/bta_gatts_act.cc b/system/bta/gatt/bta_gatts_act.cc
index 7f2457e..800fbf0 100644
--- a/system/bta/gatt/bta_gatts_act.cc
+++ b/system/bta/gatt/bta_gatts_act.cc
@@ -120,6 +120,8 @@
p_cb->enabled = true;
+ gatt_load_bonded();
+
if (!GATTS_NVRegister(&bta_gatts_nv_cback)) {
LOG(ERROR) << "BTA GATTS NV register failed.";
}
diff --git a/system/bta/gatt/bta_gatts_api.cc b/system/bta/gatt/bta_gatts_api.cc
index 0de36db..5c69ab4 100644
--- a/system/bta/gatt/bta_gatts_api.cc
+++ b/system/bta/gatt/bta_gatts_api.cc
@@ -242,6 +242,12 @@
void BTA_GATTS_HandleValueIndication(uint16_t conn_id, uint16_t attr_id,
std::vector<uint8_t> value,
bool need_confirm) {
+
+ if (value.size() > sizeof(tBTA_GATTS_API_INDICATION::value)) {
+ LOG(ERROR) << __func__ << "data to indicate is too long";
+ return;
+ }
+
tBTA_GATTS_API_INDICATION* p_buf =
(tBTA_GATTS_API_INDICATION*)osi_calloc(sizeof(tBTA_GATTS_API_INDICATION));
@@ -368,3 +374,11 @@
bta_sys_sendmsg(p_buf);
}
+
+void BTA_GATTS_InitBonded(void) {
+ LOG(INFO) << __func__;
+
+ BT_HDR_RIGID* p_buf = (BT_HDR_RIGID*)osi_malloc(sizeof(BT_HDR_RIGID));
+ p_buf->event = BTA_GATTS_API_INIT_BONDED_EVT;
+ bta_sys_sendmsg(p_buf);
+}
diff --git a/system/bta/gatt/bta_gatts_int.h b/system/bta/gatt/bta_gatts_int.h
index f989e09..7672130 100644
--- a/system/bta/gatt/bta_gatts_int.h
+++ b/system/bta/gatt/bta_gatts_int.h
@@ -51,7 +51,9 @@
BTA_GATTS_API_OPEN_EVT,
BTA_GATTS_API_CANCEL_OPEN_EVT,
BTA_GATTS_API_CLOSE_EVT,
- BTA_GATTS_API_DISABLE_EVT
+ BTA_GATTS_API_DISABLE_EVT,
+
+ BTA_GATTS_API_INIT_BONDED_EVT,
};
typedef uint16_t tBTA_GATTS_INT_EVT;
diff --git a/system/bta/gatt/bta_gatts_main.cc b/system/bta/gatt/bta_gatts_main.cc
index 97e9a3d..458f5e2 100644
--- a/system/bta/gatt/bta_gatts_main.cc
+++ b/system/bta/gatt/bta_gatts_main.cc
@@ -105,6 +105,10 @@
break;
}
+ case BTA_GATTS_API_INIT_BONDED_EVT:
+ gatt_load_bonded();
+ break;
+
default:
break;
}
diff --git a/system/bta/has/has_client.cc b/system/bta/has/has_client.cc
index 43389e1..02d0a23 100644
--- a/system/bta/has/has_client.cc
+++ b/system/bta/has/has_client.cc
@@ -84,6 +84,7 @@
uint8_t features);
void btif_storage_set_leaudio_has_active_preset(const RawAddress& address,
uint8_t active_preset);
+void btif_storage_remove_leaudio_has(const RawAddress& address);
extern bool gatt_profile_get_eatt_support(const RawAddress& remote_bda);
@@ -309,6 +310,11 @@
auto op = op_opt.value();
callbacks_->OnActivePresetSelectError(op.addr_or_group,
GattStatus2SvcErrorCode(status));
+
+ if (status == GATT_DATABASE_OUT_OF_SYNC) {
+ LOG_INFO("Database out of sync for %s", device->addr.ToString().c_str());
+ ClearDeviceInformationAndStartSearch(device);
+ }
}
void OnHasPresetNameSetStatus(uint16_t conn_id, tGATT_STATUS status,
@@ -339,6 +345,10 @@
auto op = op_opt.value();
callbacks_->OnSetPresetNameError(device->addr, op.index,
GattStatus2SvcErrorCode(status));
+ if (status == GATT_DATABASE_OUT_OF_SYNC) {
+ LOG_INFO("Database out of sync for %s", device->addr.ToString().c_str());
+ ClearDeviceInformationAndStartSearch(device);
+ }
}
void OnHasPresetNameGetStatus(uint16_t conn_id, tGATT_STATUS status,
@@ -367,9 +377,14 @@
callbacks_->OnPresetInfoError(device->addr, op.index,
GattStatus2SvcErrorCode(status));
- LOG_ERROR("Devices %s: Control point not usable. Disconnecting!",
- device->addr.ToString().c_str());
- BTA_GATTC_Close(conn_id);
+ if (status == GATT_DATABASE_OUT_OF_SYNC) {
+ LOG_INFO("Database out of sync for %s", device->addr.ToString().c_str());
+ ClearDeviceInformationAndStartSearch(device);
+ } else {
+ LOG_ERROR("Devices %s: Control point not usable. Disconnecting!",
+ device->addr.ToString().c_str());
+ BTA_GATTC_Close(device->conn_id);
+ }
}
void OnHasPresetIndexOperation(uint16_t conn_id, tGATT_STATUS status,
@@ -409,9 +424,15 @@
callbacks_->OnActivePresetSelectError(op.addr_or_group,
GattStatus2SvcErrorCode(status));
}
- LOG_ERROR("Devices %s: Control point not usable. Disconnecting!",
- device->addr.ToString().c_str());
- BTA_GATTC_Close(conn_id);
+
+ if (status == GATT_DATABASE_OUT_OF_SYNC) {
+ LOG_INFO("Database out of sync for %s", device->addr.ToString().c_str());
+ ClearDeviceInformationAndStartSearch(device);
+ } else {
+ LOG_ERROR("Devices %s: Control point not usable. Disconnecting!",
+ device->addr.ToString().c_str());
+ BTA_GATTC_Close(device->conn_id);
+ }
}
void CpReadAllPresetsOperation(HasCtpOp operation) {
@@ -988,6 +1009,12 @@
return;
}
+ if (status == GATT_DATABASE_OUT_OF_SYNC) {
+ LOG_INFO("Database out of sync for %s", device->addr.ToString().c_str());
+ ClearDeviceInformationAndStartSearch(device);
+ return;
+ }
+
HasGattOpContext context(user_data);
bool enabling_ntf = context.context_flags &
HasGattOpContext::kContextFlagsEnableNotification;
@@ -1058,9 +1085,14 @@
}
if (status != GATT_SUCCESS) {
- LOG(ERROR) << __func__ << ": Could not read characteristic at handle="
- << loghex(handle);
- BTA_GATTC_Close(device->conn_id);
+ if (status == GATT_DATABASE_OUT_OF_SYNC) {
+ LOG_INFO("Database out of sync for %s",
+ device->addr.ToString().c_str());
+ ClearDeviceInformationAndStartSearch(device);
+ } else {
+ LOG_ERROR("Could not read characteristic at handle=0x%04x", handle);
+ BTA_GATTC_Close(device->conn_id);
+ }
return;
}
@@ -1434,10 +1466,14 @@
}
if (status != GATT_SUCCESS) {
- LOG(ERROR) << __func__ << ": Could not read characteristic at handle="
- << loghex(handle);
- BTA_GATTC_Close(device->conn_id);
- return;
+ if (status == GATT_DATABASE_OUT_OF_SYNC) {
+ LOG_INFO("Database out of sync for %s",
+ device->addr.ToString().c_str());
+ ClearDeviceInformationAndStartSearch(device);
+ } else {
+ LOG_ERROR("Could not read characteristic at handle=0x%04x", handle);
+ BTA_GATTC_Close(device->conn_id);
+ }
}
if (len != 1) {
@@ -1507,11 +1543,7 @@
}
}
- /* Cleans up after the device disconnection */
- void DoDisconnectCleanUp(HasDevice& device,
- bool invalidate_gatt_service = true) {
- DLOG(INFO) << __func__ << ": device=" << device.addr;
-
+ void DeregisterNotifications(HasDevice& device) {
/* Deregister from optional features notifications */
if (device.features_ccc_handle != GAP_INVALID_HANDLE) {
BTA_GATTC_DeregisterForNotifications(gatt_if_, device.addr,
@@ -1529,6 +1561,14 @@
BTA_GATTC_DeregisterForNotifications(gatt_if_, device.addr,
device.cp_handle);
}
+ }
+
+ /* Cleans up after the device disconnection */
+ void DoDisconnectCleanUp(HasDevice& device,
+ bool invalidate_gatt_service = true) {
+ LOG_DEBUG(": device=%s", device.addr.ToString().c_str());
+
+ DeregisterNotifications(device);
if (device.conn_id != GATT_INVALID_CONN_ID) {
BtaGattQueue::Clean(device.conn_id);
@@ -1940,6 +1980,27 @@
}
}
+ void ClearDeviceInformationAndStartSearch(HasDevice* device) {
+ if (!device) {
+ LOG_ERROR("Device is null");
+ return;
+ }
+
+ LOG_INFO("%s", device->addr.ToString().c_str());
+
+ if (!device->isGattServiceValid()) {
+ LOG_INFO("Service already invalidated");
+ return;
+ }
+
+ /* Invalidate service discovery results */
+ DeregisterNotifications(*device);
+ BtaGattQueue::Clean(device->conn_id);
+ device->ClearSvcData();
+ btif_storage_remove_leaudio_has(device->addr);
+ BTA_GATTC_ServiceSearchRequest(device->conn_id, &kUuidHearingAccessService);
+ }
+
void OnGattServiceChangeEvent(const RawAddress& address) {
auto device = std::find_if(devices_.begin(), devices_.end(),
HasDevice::MatchAddress(address));
@@ -1947,12 +2008,8 @@
LOG(WARNING) << "Skipping unknown device" << address;
return;
}
-
- DLOG(INFO) << __func__ << ": address=" << address;
-
- /* Invalidate service discovery results */
- BtaGattQueue::Clean(device->conn_id);
- device->ClearSvcData();
+ LOG_INFO("%s", address.ToString().c_str());
+ ClearDeviceInformationAndStartSearch(&(*device));
}
void OnGattServiceDiscoveryDoneEvent(const RawAddress& address) {
diff --git a/system/bta/has/has_client_test.cc b/system/bta/has/has_client_test.cc
index 520959e..ca8ca63 100644
--- a/system/bta/has/has_client_test.cc
+++ b/system/bta/has/has_client_test.cc
@@ -21,7 +21,6 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <osi/include/alarm.h>
-#include <osi/test/alarm_mock.h>
#include <sys/socket.h>
#include <variant>
@@ -39,19 +38,10 @@
#include "mock_controller.h"
#include "mock_csis_client.h"
-static std::map<const char*, bool> fake_osi_bool_props;
-
-bool osi_property_get_bool(const char* key, bool default_value) {
- if (fake_osi_bool_props.count(key)) return fake_osi_bool_props.at(key);
-
- return default_value;
-}
-
-void osi_property_set_bool(const char* key, bool value) {
- fake_osi_bool_props.insert_or_assign(key, value);
-}
-
bool gatt_profile_get_eatt_support(const RawAddress& addr) { return true; }
+void osi_property_set_bool(const char* key, bool value);
+
+std::map<std::string, int> mock_function_count_map;
namespace bluetooth {
namespace has {
@@ -655,8 +645,7 @@
}
void SetUp(void) override {
- fake_osi_bool_props.clear();
-
+ mock_function_count_map.clear();
controller::SetMockControllerInterface(&controller_interface_);
bluetooth::manager::SetMockBtmInterface(&btm_interface);
bluetooth::storage::SetMockBtifStorageInterface(&btif_storage_interface_);
@@ -2937,9 +2926,52 @@
ASSERT_TRUE(SimpleJsonValidator(sv[1], &dumpsys_byte_cnt));
}
+TEST_F(HasClientTest, test_connect_database_out_of_sync) {
+ osi_property_set_bool("persist.bluetooth.has.always_use_preset_cache", false);
+
+ const RawAddress test_address = GetTestAddress(1);
+ std::set<HasPreset, HasPreset::ComparatorDesc> has_presets = {{
+ HasPreset(1, HasPreset::kPropertyAvailable, "Universal"),
+ HasPreset(2, HasPreset::kPropertyAvailable | HasPreset::kPropertyWritable,
+ "Preset2"),
+ }};
+ SetSampleDatabaseHasPresetsNtf(
+ test_address,
+ bluetooth::has::kFeatureBitHearingAidTypeBanded |
+ bluetooth::has::kFeatureBitWritablePresets |
+ bluetooth::has::kFeatureBitDynamicPresets,
+ has_presets);
+
+ EXPECT_CALL(*callbacks, OnDeviceAvailable(
+ test_address,
+ bluetooth::has::kFeatureBitHearingAidTypeBanded |
+ bluetooth::has::kFeatureBitWritablePresets |
+ bluetooth::has::kFeatureBitDynamicPresets));
+ EXPECT_CALL(*callbacks,
+ OnConnectionState(ConnectionState::CONNECTED, test_address));
+ TestConnect(test_address);
+
+ ON_CALL(gatt_queue, WriteCharacteristic(_, _, _, _, _, _))
+ .WillByDefault(
+ Invoke([this](uint16_t conn_id, uint16_t handle,
+ std::vector<uint8_t> value, tGATT_WRITE_TYPE write_type,
+ GATT_WRITE_OP_CB cb, void* cb_data) {
+ auto* svc = gatt::FindService(services_map[conn_id], handle);
+ if (svc == nullptr) return;
+
+ tGATT_STATUS status = GATT_DATABASE_OUT_OF_SYNC;
+ if (cb)
+ cb(conn_id, status, handle, value.size(), value.data(), cb_data);
+ }));
+
+ ON_CALL(gatt_interface, ServiceSearchRequest(_, _)).WillByDefault(Return());
+ EXPECT_CALL(gatt_interface, ServiceSearchRequest(_, _));
+ HasClient::Get()->GetPresetInfo(test_address, 1);
+}
+
class HasTypesTest : public ::testing::Test {
protected:
- void SetUp(void) override { fake_osi_bool_props.clear(); }
+ void SetUp(void) override { mock_function_count_map.clear(); }
void TearDown(void) override {}
}; // namespace
@@ -3076,15 +3108,16 @@
auto address1 = GetTestAddress(1);
auto address2 = GetTestAddress(2);
- EXPECT_CALL(*AlarmMock::Get(), AlarmNew(_)).Times(1);
HasCtpGroupOpCoordinator wrapper(
{address1, address2},
HasCtpOp(0x01, ::le_audio::has::PresetCtpOpcode::READ_PRESETS, 6));
ASSERT_EQ(2u, wrapper.ref_cnt);
- EXPECT_CALL(*AlarmMock::Get(), AlarmFree(_)).Times(1);
HasCtpGroupOpCoordinator::Cleanup();
ASSERT_EQ(0u, wrapper.ref_cnt);
+
+ ASSERT_EQ(1, mock_function_count_map["alarm_free"]);
+ ASSERT_EQ(1, mock_function_count_map["alarm_new"]);
}
TEST_F(HasTypesTest, test_group_op_coordinator_copy) {
@@ -3095,7 +3128,6 @@
auto address1 = GetTestAddress(1);
auto address2 = GetTestAddress(2);
- EXPECT_CALL(*AlarmMock::Get(), AlarmNew(_)).Times(1);
HasCtpGroupOpCoordinator wrapper(
{address1, address2},
HasCtpOp(0x01, ::le_audio::has::PresetCtpOpcode::READ_PRESETS, 6));
@@ -3111,9 +3143,11 @@
delete wrapper4;
ASSERT_EQ(4u, wrapper.ref_cnt);
- EXPECT_CALL(*AlarmMock::Get(), AlarmFree(_)).Times(1);
HasCtpGroupOpCoordinator::Cleanup();
ASSERT_EQ(0u, wrapper.ref_cnt);
+
+ ASSERT_EQ(1, mock_function_count_map["alarm_free"]);
+ ASSERT_EQ(1, mock_function_count_map["alarm_new"]);
}
TEST_F(HasTypesTest, test_group_op_coordinator_completion) {
@@ -3126,7 +3160,6 @@
auto address2 = GetTestAddress(2);
auto address3 = GetTestAddress(3);
- EXPECT_CALL(*AlarmMock::Get(), AlarmNew(_)).Times(1);
HasCtpGroupOpCoordinator wrapper(
{address1, address3},
HasCtpOp(0x01, ::le_audio::has::PresetCtpOpcode::READ_PRESETS, 6));
@@ -3135,7 +3168,6 @@
HasCtpOp(0x01, ::le_audio::has::PresetCtpOpcode::READ_PRESETS, 6));
ASSERT_EQ(3u, wrapper.ref_cnt);
- EXPECT_CALL(*AlarmMock::Get(), AlarmFree(_)).Times(0);
ASSERT_FALSE(wrapper.IsFullyCompleted());
wrapper.SetCompleted(address1);
@@ -3144,23 +3176,20 @@
wrapper.SetCompleted(address3);
ASSERT_EQ(1u, wrapper.ref_cnt);
ASSERT_FALSE(wrapper.IsFullyCompleted());
- Mock::VerifyAndClearExpectations(&*AlarmMock::Get());
/* Non existing address completion */
- EXPECT_CALL(*AlarmMock::Get(), AlarmFree(_)).Times(0);
wrapper.SetCompleted(address2);
- Mock::VerifyAndClearExpectations(&*AlarmMock::Get());
ASSERT_EQ(1u, wrapper.ref_cnt);
/* Last device address completion */
- EXPECT_CALL(*AlarmMock::Get(), AlarmFree(_)).Times(1);
wrapper2.SetCompleted(address2);
- Mock::VerifyAndClearExpectations(&*AlarmMock::Get());
ASSERT_TRUE(wrapper.IsFullyCompleted());
ASSERT_EQ(0u, wrapper.ref_cnt);
- EXPECT_CALL(*AlarmMock::Get(), AlarmFree(_)).Times(0);
HasCtpGroupOpCoordinator::Cleanup();
+
+ ASSERT_EQ(1, mock_function_count_map["alarm_free"]);
+ ASSERT_EQ(1, mock_function_count_map["alarm_new"]);
}
} // namespace
diff --git a/system/bta/hd/bta_hd_api.cc b/system/bta/hd/bta_hd_api.cc
index a0d4802..93a9155 100644
--- a/system/bta/hd/bta_hd_api.cc
+++ b/system/bta/hd/bta_hd_api.cc
@@ -79,7 +79,10 @@
void BTA_HdDisable(void) {
APPL_TRACE_API("%s", __func__);
- bta_sys_deregister(BTA_ID_HD);
+ if (!bluetooth::common::init_flags::
+ delay_hidh_cleanup_until_hidh_ready_start_is_enabled()) {
+ bta_sys_deregister(BTA_ID_HD);
+ }
BT_HDR_RIGID* p_buf = (BT_HDR_RIGID*)osi_malloc(sizeof(BT_HDR_RIGID));
p_buf->event = BTA_HD_API_DISABLE_EVT;
diff --git a/system/bta/hearing_aid/hearing_aid.cc b/system/bta/hearing_aid/hearing_aid.cc
index d79e33d..8637f89 100644
--- a/system/bta/hearing_aid/hearing_aid.cc
+++ b/system/bta/hearing_aid/hearing_aid.cc
@@ -182,7 +182,8 @@
int read_rssi_start_interval_count = 0;
for (auto& d : devices) {
- VLOG(1) << __func__ << ": device=" << d.address << ", read_rssi_count=" << d.read_rssi_count;
+ LOG_DEBUG("device=%s, read_rssi_count=%d",
+ d.address.ToStringForLogging().c_str(), d.read_rssi_count);
// Reset the count
if (d.read_rssi_count <= 0) {
@@ -211,9 +212,8 @@
uint16_t handle, uint16_t len,
const uint8_t* value, void* data) {
if (status != GATT_SUCCESS) {
- LOG(ERROR) << __func__ << ": handle=" << handle << ", conn_id=" << conn_id
- << ", status=" << loghex(static_cast<uint8_t>(status))
- << ", length=" << len;
+ LOG_ERROR("handle= %hu, conn_id=%hu, status= %s, length=%u", handle,
+ conn_id, loghex(static_cast<uint8_t>(status)).c_str(), len);
}
}
@@ -222,7 +222,7 @@
inline void encoder_state_init() {
if (encoder_state_left != nullptr) {
- LOG(WARNING) << __func__ << ": encoder already initialized";
+ LOG_WARN("encoder already initialized");
return;
}
encoder_state_left = g722_encode_init(nullptr, 64000, G722_PACKED);
@@ -261,19 +261,16 @@
"persist.bluetooth.hearingaid.interval", (int32_t)HA_INTERVAL_20_MS);
if ((default_data_interval_ms != HA_INTERVAL_10_MS) &&
(default_data_interval_ms != HA_INTERVAL_20_MS)) {
- LOG(ERROR) << __func__
- << ": invalid interval=" << default_data_interval_ms
- << "ms. Overwriting back to default";
+ LOG_ERROR("invalid interval= %ums. Overwrriting back to default",
+ default_data_interval_ms);
default_data_interval_ms = HA_INTERVAL_20_MS;
}
- VLOG(2) << __func__
- << ", default_data_interval_ms=" << default_data_interval_ms;
+ LOG_DEBUG("default_data_interval_ms=%u", default_data_interval_ms);
overwrite_min_ce_len = (uint16_t)osi_property_get_int32(
"persist.bluetooth.hearingaidmincelen", 0);
if (overwrite_min_ce_len) {
- LOG(INFO) << __func__
- << ": Overwrites MIN_CE_LEN=" << overwrite_min_ce_len;
+ LOG_INFO("Overwrites MIN_CE_LEN=%u", overwrite_min_ce_len);
}
BTA_GATTC_AppRegister(
@@ -281,14 +278,15 @@
base::Bind(
[](Closure initCb, uint8_t client_id, uint8_t status) {
if (status != GATT_SUCCESS) {
- LOG(ERROR) << "Can't start Hearing Aid profile - no gatt "
- "clients left!";
+ LOG_ERROR(
+ "Can't start Hearing Aid profile - no gatt clients left!");
return;
}
instance->gatt_if = client_id;
initCb.Run();
},
- initCb), false);
+ initCb),
+ false);
}
uint16_t UpdateBleConnParams(const RawAddress& address) {
@@ -306,15 +304,15 @@
connection_interval = CONNECTION_INTERVAL_20MS_PARAM;
break;
default:
- LOG(ERROR) << __func__ << ":Error: invalid default_data_interval_ms="
- << default_data_interval_ms;
+ LOG_ERROR("invalid default_data_interval_ms=%u",
+ default_data_interval_ms);
min_ce_len = MIN_CE_LEN_10MS_CI;
connection_interval = CONNECTION_INTERVAL_10MS_PARAM;
}
if (overwrite_min_ce_len != 0) {
- VLOG(2) << __func__ << ": min_ce_len=" << min_ce_len
- << " is overwritten to " << overwrite_min_ce_len;
+ LOG_DEBUG("min_ce_len=%u is overwritten to %u", min_ce_len,
+ overwrite_min_ce_len);
min_ce_len = overwrite_min_ce_len;
}
@@ -323,22 +321,22 @@
return connection_interval;
}
- void Connect(const RawAddress& address) override {
- DVLOG(2) << __func__ << " " << address;
+ void Connect(const RawAddress& address) {
+ LOG_DEBUG("%s", address.ToStringForLogging().c_str());
hearingDevices.Add(HearingDevice(address, true));
BTA_GATTC_Open(gatt_if, address, BTM_BLE_DIRECT_CONNECTION, false);
}
- void AddToAcceptlist(const RawAddress& address) override {
- VLOG(2) << __func__ << " address: " << address;
+ void AddToAcceptlist(const RawAddress& address) {
+ LOG_DEBUG("%s", address.ToStringForLogging().c_str());
hearingDevices.Add(HearingDevice(address, true));
BTA_GATTC_Open(gatt_if, address, BTM_BLE_BKG_CONNECT_ALLOW_LIST, false);
}
void AddFromStorage(const HearingDevice& dev_info, uint16_t is_acceptlisted) {
- DVLOG(2) << __func__ << " " << dev_info.address
- << ", hiSyncId=" << loghex(dev_info.hi_sync_id)
- << ", isAcceptlisted=" << is_acceptlisted;
+ LOG_DEBUG("%s, hiSyncId=%s, isAcceptlisted=%u",
+ dev_info.address.ToStringForLogging().c_str(),
+ loghex(dev_info.hi_sync_id).c_str(), is_acceptlisted);
if (is_acceptlisted) {
hearingDevices.Add(dev_info);
@@ -364,13 +362,14 @@
if (!hearingDevice) {
/* When Hearing Aid is quickly disabled and enabled in settings, this case
* might happen */
- LOG(WARNING) << "Closing connection to non hearing-aid device, address="
- << address;
+ LOG_WARN("Closing connection to non hearing-aid device, address=%s",
+ address.ToStringForLogging().c_str());
BTA_GATTC_Close(conn_id);
return;
}
- LOG(INFO) << __func__ << ": address=" << address << ", conn_id=" << conn_id;
+ LOG_INFO("address=%s, conn_id=%u", address.ToStringForLogging().c_str(),
+ conn_id);
if (status != GATT_SUCCESS) {
if (!hearingDevice->connecting_actively) {
@@ -378,7 +377,7 @@
return;
}
- LOG(INFO) << "Failed to connect to Hearing Aid device";
+ LOG_INFO("Failed to connect to Hearing Aid device");
hearingDevices.Remove(address);
callbacks->OnConnectionState(ConnectionState::DISCONNECTED, address);
return;
@@ -404,7 +403,7 @@
}
if (controller_get_interface()->supports_ble_2m_phy()) {
- LOG(INFO) << address << " set preferred 2M PHY";
+ LOG_INFO("%s set preferred 2M PHY", address.ToStringForLogging().c_str());
BTM_BleSetPhy(address, PHY_LE_2M, PHY_LE_2M, 0);
}
@@ -439,7 +438,7 @@
void OnConnectionUpdateComplete(uint16_t conn_id, tBTA_GATTC* p_data) {
HearingDevice* hearingDevice = hearingDevices.FindByConnId(conn_id);
if (!hearingDevice) {
- DVLOG(2) << "Skipping unknown device, conn_id=" << loghex(conn_id);
+ LOG_DEBUG("Skipping unknown device, conn_id=%s", loghex(conn_id).c_str());
return;
}
@@ -452,31 +451,29 @@
switch (hearingDevice->connection_update_status) {
case COMPLETED:
if (!same_conn_interval) {
- LOG(WARNING) << __func__
- << ": Unexpected change. Redo. connection interval="
- << p_data->conn_update.interval << ", expected="
- << hearingDevice->requested_connection_interval
- << ", conn_id=" << conn_id
- << ", connection_update_status="
- << hearingDevice->connection_update_status;
+ LOG_WARN(
+ "Unexpected change. Redo. connection interval=%u, "
+ "expected=%u, conn_id=%u, connection_update_status=%u",
+ p_data->conn_update.interval,
+ hearingDevice->requested_connection_interval, conn_id,
+ hearingDevice->connection_update_status);
// Redo this connection interval change.
hearingDevice->connection_update_status = AWAITING;
}
break;
case STARTED:
if (same_conn_interval) {
- LOG(INFO) << __func__
- << ": Connection update completed. conn_id=" << conn_id
- << ", device=" << hearingDevice->address;
+ LOG_INFO("Connection update completed. conn_id=%u, device=%s",
+ conn_id,
+ hearingDevice->address.ToStringForLogging().c_str());
hearingDevice->connection_update_status = COMPLETED;
} else {
- LOG(WARNING) << __func__
- << ": Ignored. Different connection interval="
- << p_data->conn_update.interval << ", expected="
- << hearingDevice->requested_connection_interval
- << ", conn_id=" << conn_id
- << ", connection_update_status="
- << hearingDevice->connection_update_status;
+ LOG_WARN(
+ "Ignored. Different connection interval=%u, expected=%u, "
+ "conn_id=%u, connection_update_status=%u",
+ p_data->conn_update.interval,
+ hearingDevice->requested_connection_interval, conn_id,
+ hearingDevice->connection_update_status);
// Wait for the right Connection Update Completion.
return;
}
@@ -494,16 +491,15 @@
send_state_change_to_other_side(hearingDevice, conn_update);
send_state_change(hearingDevice, conn_update);
} else {
- LOG(INFO) << __func__ << ": error status="
- << loghex(static_cast<uint8_t>(p_data->conn_update.status))
- << ", conn_id=" << conn_id
- << ", device=" << hearingDevice->address
- << ", connection_update_status="
- << hearingDevice->connection_update_status;
-
+ LOG_INFO(
+ "error status=%s, conn_id=%u,device=%s, "
+ "connection_update_status=%u",
+ loghex(static_cast<uint8_t>(p_data->conn_update.status)).c_str(),
+ conn_id, hearingDevice->address.ToStringForLogging().c_str(),
+ hearingDevice->connection_update_status);
if (hearingDevice->connection_update_status == STARTED) {
// Redo this connection interval change.
- LOG(ERROR) << __func__ << ": Redo Connection Interval change";
+ LOG_ERROR("Redo Connection Interval change");
hearingDevice->connection_update_status = AWAITING;
}
}
@@ -531,15 +527,18 @@
void OnReadRssiComplete(const RawAddress& address, int8_t rssi_value) {
HearingDevice* hearingDevice = hearingDevices.FindByAddress(address);
if (!hearingDevice) {
- LOG(INFO) << "Skipping unknown device" << address;
+ LOG_INFO("Skipping unknown device %s",
+ address.ToStringForLogging().c_str());
return;
}
- VLOG(1) << __func__ << ": device=" << address << ", rssi=" << (int)rssi_value;
+ LOG_DEBUG("device=%s, rss=%d", address.ToStringForLogging().c_str(),
+ (int)rssi_value);
if (hearingDevice->read_rssi_count <= 0) {
- LOG(ERROR) << __func__ << ": device=" << address
- << ", invalid read_rssi_count=" << hearingDevice->read_rssi_count;
+ LOG_ERROR(" device=%s, invalid read_rssi_count=%d",
+ address.ToStringForLogging().c_str(),
+ hearingDevice->read_rssi_count);
return;
}
@@ -548,7 +547,8 @@
if (hearingDevice->read_rssi_count == READ_RSSI_NUM_TRIES) {
// Store the timestamp only for the first one after packet flush
clock_gettime(CLOCK_REALTIME, &last_log_set.timestamp);
- LOG(INFO) << __func__ << ": store time. device=" << address << ", rssi=" << (int)rssi_value;
+ LOG_INFO("store time, device=%s, rssi=%d",
+ address.ToStringForLogging().c_str(), (int)rssi_value);
}
last_log_set.rssi.emplace_back(rssi_value);
@@ -558,12 +558,13 @@
void OnEncryptionComplete(const RawAddress& address, bool success) {
HearingDevice* hearingDevice = hearingDevices.FindByAddress(address);
if (!hearingDevice) {
- DVLOG(2) << "Skipping unknown device" << address;
+ LOG_DEBUG("Skipping unknown device %s",
+ address.ToStringForLogging().c_str());
return;
}
if (!success) {
- LOG(ERROR) << "encryption failed";
+ LOG_ERROR("encryption failed");
BTA_GATTC_Close(hearingDevice->conn_id);
if (hearingDevice->first_connection) {
callbacks->OnConnectionState(ConnectionState::DISCONNECTED, address);
@@ -571,7 +572,7 @@
return;
}
- LOG(INFO) << __func__ << ": " << address;
+ LOG_INFO("%s", address.ToStringForLogging().c_str());
if (hearingDevice->audio_control_point_handle &&
hearingDevice->audio_status_handle &&
@@ -580,8 +581,8 @@
// Use cached data, jump to read PSM
ReadPSM(hearingDevice);
} else {
- LOG(INFO) << __func__ << ": " << address
- << ": do BTA_GATTC_ServiceSearchRequest";
+ LOG_INFO("%s: do BTA_GATTC_ServiceSearchRequest",
+ address.ToStringForLogging().c_str());
hearingDevice->first_connection = true;
BTA_GATTC_ServiceSearchRequest(hearingDevice->conn_id, &HEARING_AID_UUID);
}
@@ -592,32 +593,34 @@
tGATT_STATUS status) {
HearingDevice* hearingDevice = hearingDevices.FindByConnId(conn_id);
if (!hearingDevice) {
- DVLOG(2) << "Skipping unknown device, conn_id=" << loghex(conn_id);
+ LOG_DEBUG("Skipping unknown device, conn_id=%s", loghex(conn_id).c_str());
return;
}
if (status != GATT_SUCCESS) {
- LOG(WARNING) << hearingDevice->address
- << " phy update fail with status: " << status;
+ LOG_WARN("%s phy update fail with status: %hu",
+ hearingDevice->address.ToStringForLogging().c_str(), status);
return;
}
if (tx_phys == PHY_LE_2M && rx_phys == PHY_LE_2M) {
- LOG(INFO) << hearingDevice->address << " phy update to 2M successful";
+ LOG_INFO("%s phy update to 2M successful",
+ hearingDevice->address.ToStringForLogging().c_str());
return;
}
- LOG(INFO)
- << hearingDevice->address
- << " phy update successful but not target phy, try again. tx_phys: "
- << tx_phys << ", rx_phys: " << rx_phys;
+ LOG_INFO(
+ "%s phy update successful but not target phy, try again. tx_phys: "
+ "%u,rx_phys: %u",
+ hearingDevice->address.ToStringForLogging().c_str(), tx_phys, rx_phys);
BTM_BleSetPhy(hearingDevice->address, PHY_LE_2M, PHY_LE_2M, 0);
}
void OnServiceChangeEvent(const RawAddress& address) {
HearingDevice* hearingDevice = hearingDevices.FindByAddress(address);
if (!hearingDevice) {
- VLOG(2) << "Skipping unknown device" << address;
+ LOG_DEBUG("Skipping unknown device %s",
+ address.ToStringForLogging().c_str());
return;
}
- LOG(INFO) << __func__ << ": address=" << address;
+ LOG_INFO("address=%s", address.ToStringForLogging().c_str());
hearingDevice->first_connection = true;
hearingDevice->service_changed_rcvd = true;
BtaGattQueue::Clean(hearingDevice->conn_id);
@@ -630,17 +633,18 @@
void OnServiceDiscDoneEvent(const RawAddress& address) {
HearingDevice* hearingDevice = hearingDevices.FindByAddress(address);
if (!hearingDevice) {
- VLOG(2) << "Skipping unknown device" << address;
+ LOG_DEBUG("Skipping unknown device %s",
+ address.ToStringForLogging().c_str());
return;
}
- LOG(INFO) << __func__ << ": " << address;
+ LOG_INFO("%s", address.ToStringForLogging().c_str());
if (hearingDevice->service_changed_rcvd ||
!(hearingDevice->audio_control_point_handle &&
hearingDevice->audio_status_handle &&
hearingDevice->audio_status_ccc_handle &&
hearingDevice->volume_handle && hearingDevice->read_psm_handle)) {
- LOG(INFO) << __func__ << ": " << address
- << ": do BTA_GATTC_ServiceSearchRequest";
+ LOG_INFO("%s: do BTA_GATTC_ServiceSearchRequest",
+ address.ToStringForLogging().c_str());
BTA_GATTC_ServiceSearchRequest(hearingDevice->conn_id, &HEARING_AID_UUID);
}
}
@@ -648,7 +652,7 @@
void OnServiceSearchComplete(uint16_t conn_id, tGATT_STATUS status) {
HearingDevice* hearingDevice = hearingDevices.FindByConnId(conn_id);
if (!hearingDevice) {
- DVLOG(2) << "Skipping unknown device, conn_id=" << loghex(conn_id);
+ LOG_DEBUG("Skipping unknown device, conn_id=%s", loghex(conn_id).c_str());
return;
}
@@ -657,7 +661,7 @@
if (status != GATT_SUCCESS) {
/* close connection and report service discovery complete with error */
- LOG(ERROR) << "Service discovery failed";
+ LOG_ERROR("Service discovery failed");
if (hearingDevice->first_connection) {
callbacks->OnConnectionState(ConnectionState::DISCONNECTED,
hearingDevice->address);
@@ -670,18 +674,19 @@
const gatt::Service* service = nullptr;
for (const gatt::Service& tmp : *services) {
if (tmp.uuid == Uuid::From16Bit(UUID_SERVCLASS_GATT_SERVER)) {
- LOG(INFO) << "Found UUID_SERVCLASS_GATT_SERVER, handle="
- << loghex(tmp.handle);
+ LOG_INFO("Found UUID_SERVCLASS_GATT_SERVER, handle=%s",
+ loghex(tmp.handle).c_str());
const gatt::Service* service_changed_service = &tmp;
find_server_changed_ccc_handle(conn_id, service_changed_service);
} else if (tmp.uuid == HEARING_AID_UUID) {
- LOG(INFO) << "Found Hearing Aid service, handle=" << loghex(tmp.handle);
+ LOG_INFO("Found Hearing Aid service, handle=%s",
+ loghex(tmp.handle).c_str());
service = &tmp;
}
}
if (!service) {
- LOG(ERROR) << "No Hearing Aid service found";
+ LOG_ERROR("No Hearing Aid service found");
callbacks->OnConnectionState(ConnectionState::DISCONNECTED,
hearingDevice->address);
return;
@@ -693,8 +698,8 @@
hearingDevice->address, &hearingDevice->capabilities,
&hearingDevice->hi_sync_id, &hearingDevice->render_delay,
&hearingDevice->preparation_delay, &hearingDevice->codecs)) {
- VLOG(2) << "Reading read only properties "
- << loghex(charac.value_handle);
+ LOG_DEBUG("Reading read only properties %s",
+ loghex(charac.value_handle).c_str());
BtaGattQueue::ReadCharacteristic(
conn_id, charac.value_handle,
HearingAidImpl::OnReadOnlyPropertiesReadStatic, nullptr);
@@ -708,19 +713,20 @@
hearingDevice->audio_status_ccc_handle =
find_ccc_handle(conn_id, charac.value_handle);
if (!hearingDevice->audio_status_ccc_handle) {
- LOG(ERROR) << __func__ << ": cannot find Audio Status CCC descriptor";
+ LOG_ERROR("cannot find Audio Status CCC descriptor");
continue;
}
- LOG(INFO) << __func__
- << ": audio_status_handle=" << loghex(charac.value_handle)
- << ", ccc=" << loghex(hearingDevice->audio_status_ccc_handle);
+ LOG_INFO("audio_status_handle=%s, ccc=%s",
+ loghex(charac.value_handle).c_str(),
+ loghex(hearingDevice->audio_status_ccc_handle).c_str());
} else if (charac.uuid == VOLUME_UUID) {
hearingDevice->volume_handle = charac.value_handle;
} else if (charac.uuid == LE_PSM_UUID) {
hearingDevice->read_psm_handle = charac.value_handle;
} else {
- LOG(WARNING) << "Unknown characteristic found:" << charac.uuid;
+ LOG_WARN("Unknown characteristic found:%s",
+ charac.uuid.ToString().c_str());
}
}
@@ -733,8 +739,9 @@
void ReadPSM(HearingDevice* hearingDevice) {
if (hearingDevice->read_psm_handle) {
- LOG(INFO) << "Reading PSM " << loghex(hearingDevice->read_psm_handle)
- << ", device=" << hearingDevice->address;
+ LOG_INFO("Reading PSM %s, device=%s",
+ loghex(hearingDevice->read_psm_handle).c_str(),
+ hearingDevice->address.ToStringForLogging().c_str());
BtaGattQueue::ReadCharacteristic(
hearingDevice->conn_id, hearingDevice->read_psm_handle,
HearingAidImpl::OnPsmReadStatic, nullptr);
@@ -745,33 +752,29 @@
uint8_t* value) {
HearingDevice* device = hearingDevices.FindByConnId(conn_id);
if (!device) {
- LOG(INFO) << __func__
- << ": Skipping unknown device, conn_id=" << loghex(conn_id);
+ LOG_INFO("Skipping unknown device, conn_id=%s", loghex(conn_id).c_str());
return;
}
if (device->audio_status_handle != handle) {
- LOG(INFO) << __func__ << ": Mismatched handle, "
- << loghex(device->audio_status_handle)
- << "!=" << loghex(handle);
+ LOG_INFO("Mismatched handle, %s!=%s",
+ loghex(device->audio_status_handle).c_str(),
+ loghex(handle).c_str());
return;
}
if (len < 1) {
- LOG(ERROR) << __func__ << ": Data Length too small, len=" << len
- << ", expecting at least 1";
+ LOG_ERROR("Data Length too small, len=%u, expecting at least 1", len);
return;
}
if (value[0] != 0) {
- LOG(INFO) << __func__
- << ": Invalid returned status. data=" << loghex(value[0]);
+ LOG_INFO("Invalid returned status. data=%s", loghex(value[0]).c_str());
return;
}
- LOG(INFO) << __func__
- << ": audio status success notification. command_acked="
- << device->command_acked;
+ LOG_INFO("audio status success notification. command_acked=%u",
+ device->command_acked);
device->command_acked = true;
}
@@ -780,11 +783,11 @@
void* data) {
HearingDevice* hearingDevice = hearingDevices.FindByConnId(conn_id);
if (!hearingDevice) {
- DVLOG(2) << __func__ << "unknown conn_id=" << loghex(conn_id);
+ LOG_DEBUG("unknown conn_id=%s", loghex(conn_id).c_str());
return;
}
- VLOG(2) << __func__ << " " << base::HexEncode(value, len);
+ LOG_DEBUG("%s", base::HexEncode(value, len).c_str());
uint8_t* p = value;
@@ -792,13 +795,13 @@
STREAM_TO_UINT8(version, p);
if (version != 0x01) {
- LOG(WARNING) << "Unknown version: " << loghex(version);
+ LOG_WARN("Unknown version: %s", loghex(version).c_str());
return;
}
// version 0x01 of read only properties:
if (len < 17) {
- LOG(WARNING) << "Read only properties too short: " << loghex(len);
+ LOG_WARN("Read only properties too short: %s", loghex(len).c_str());
return;
}
uint8_t capabilities;
@@ -806,35 +809,34 @@
hearingDevice->capabilities = capabilities;
bool side = capabilities & CAPABILITY_SIDE;
bool standalone = capabilities & CAPABILITY_BINAURAL;
- VLOG(2) << __func__ << " capabilities: " << (side ? "right" : "left")
- << ", " << (standalone ? "binaural" : "monaural");
+ LOG_DEBUG("capabilities: %s, %s", (side ? "right" : "left"),
+ (standalone ? "binaural" : "monaural"));
if (capabilities & CAPABILITY_RESERVED) {
- LOG(WARNING) << __func__ << " reserved capabilities are set";
+ LOG_WARN("reserved capabilities are set");
}
STREAM_TO_UINT64(hearingDevice->hi_sync_id, p);
- VLOG(2) << __func__ << " hiSyncId: " << loghex(hearingDevice->hi_sync_id);
+ LOG_DEBUG("hiSyncId: %s", loghex(hearingDevice->hi_sync_id).c_str());
uint8_t feature_map;
STREAM_TO_UINT8(feature_map, p);
STREAM_TO_UINT16(hearingDevice->render_delay, p);
- VLOG(2) << __func__
- << " render delay: " << loghex(hearingDevice->render_delay);
+ LOG_DEBUG("render delay: %s", loghex(hearingDevice->render_delay).c_str());
STREAM_TO_UINT16(hearingDevice->preparation_delay, p);
- VLOG(2) << __func__ << " preparation delay: "
- << loghex(hearingDevice->preparation_delay);
+ LOG_DEBUG("preparation delay: %s",
+ loghex(hearingDevice->preparation_delay).c_str());
uint16_t codecs;
STREAM_TO_UINT16(codecs, p);
hearingDevice->codecs = codecs;
- VLOG(2) << __func__ << " supported codecs: " << loghex(codecs);
- if (codecs & (1 << CODEC_G722_16KHZ)) VLOG(2) << "\tG722@16kHz";
- if (codecs & (1 << CODEC_G722_24KHZ)) VLOG(2) << "\tG722@24kHz";
+ LOG_DEBUG("supported codecs: %s", loghex(codecs).c_str());
+ if (codecs & (1 << CODEC_G722_16KHZ)) LOG_INFO("%s\tG722@16kHz", __func__);
+ if (codecs & (1 << CODEC_G722_24KHZ)) LOG_INFO("%s\tG722@24kHz", __func__);
if (!(codecs & (1 << CODEC_G722_16KHZ))) {
- LOG(WARNING) << __func__ << " Mandatory codec, G722@16kHz not supported";
+ LOG_WARN("Mandatory codec, G722@16kHz not supported");
}
}
@@ -883,29 +885,31 @@
void OnAudioStatus(uint16_t conn_id, tGATT_STATUS status, uint16_t handle,
uint16_t len, uint8_t* value, void* data) {
- LOG(INFO) << __func__ << " " << base::HexEncode(value, len);
+ LOG_INFO("%s", base::HexEncode(value, len).c_str());
}
void OnPsmRead(uint16_t conn_id, tGATT_STATUS status, uint16_t handle,
uint16_t len, uint8_t* value, void* data) {
HearingDevice* hearingDevice = hearingDevices.FindByConnId(conn_id);
if (!hearingDevice) {
- DVLOG(2) << "Skipping unknown read event, conn_id=" << loghex(conn_id);
+ LOG_DEBUG("Skipping unknown read event, conn_id=%s",
+ loghex(conn_id).c_str());
return;
}
if (status != GATT_SUCCESS) {
- LOG(ERROR) << "Error reading PSM for device" << hearingDevice->address;
+ LOG_ERROR("Error reading PSM for device %s",
+ hearingDevice->address.ToStringForLogging().c_str());
return;
}
if (len > 2) {
- LOG(ERROR) << "Bad PSM length";
+ LOG_ERROR("Bad PSM Lengh");
return;
}
uint16_t psm = *((uint16_t*)value);
- VLOG(2) << "read psm:" << loghex(psm);
+ LOG_DEBUG("read psm:%s", loghex(psm).c_str());
if (hearingDevice->gap_handle == GAP_INVALID_HANDLE &&
BTM_IsEncrypted(hearingDevice->address, BT_TRANSPORT_LE)) {
@@ -926,12 +930,12 @@
&cfg_info, nullptr, BTM_SEC_NONE /* TODO: request security ? */,
HearingAidImpl::GapCallbackStatic, BT_TRANSPORT_LE);
if (gap_handle == GAP_INVALID_HANDLE) {
- LOG(ERROR) << "UNABLE TO GET gap_handle";
+ LOG_ERROR("UNABLE TO GET gap_handle");
return;
}
hearingDevice->gap_handle = gap_handle;
- LOG(INFO) << "Successfully sent GAP connect request";
+ LOG_INFO("Successfully sent GAP connect request");
}
static void OnReadOnlyPropertiesReadStatic(uint16_t conn_id,
@@ -960,7 +964,8 @@
void OnDeviceReady(const RawAddress& address) {
HearingDevice* hearingDevice = hearingDevices.FindByAddress(address);
if (!hearingDevice) {
- LOG(INFO) << "Device not connected to profile" << address;
+ LOG_INFO("Device not connected to profile %s",
+ address.ToStringForLogging().c_str());
return;
}
@@ -970,19 +975,17 @@
hearingDevice->first_connection = false;
}
- LOG(INFO) << __func__ << ": audio_status_handle="
- << loghex(hearingDevice->audio_status_handle)
- << ", audio_status_ccc_handle="
- << loghex(hearingDevice->audio_status_ccc_handle);
+ LOG_INFO("audio_status_handle=%s, audio_status_ccc_handle=%s",
+ loghex(hearingDevice->audio_status_handle).c_str(),
+ loghex(hearingDevice->audio_status_ccc_handle).c_str());
/* Register and enable the Audio Status Notification */
tGATT_STATUS register_status;
register_status = BTA_GATTC_RegisterForNotifications(
gatt_if, address, hearingDevice->audio_status_handle);
if (register_status != GATT_SUCCESS) {
- LOG(ERROR) << __func__
- << ": BTA_GATTC_RegisterForNotifications failed, status="
- << loghex(static_cast<uint8_t>(register_status));
+ LOG_ERROR("BTA_GATTC_RegisterForNotifications failed, status=%s",
+ loghex(static_cast<uint8_t>(register_status)).c_str());
return;
}
std::vector<uint8_t> value(2);
@@ -1005,10 +1008,10 @@
hearingDevice->connecting_actively = false;
hearingDevice->accepting_audio = true;
- LOG(INFO) << __func__ << ": address=" << address
- << ", hi_sync_id=" << loghex(hearingDevice->hi_sync_id)
- << ", codec_in_use=" << loghex(codec_in_use)
- << ", audio_running=" << audio_running;
+ LOG_INFO("address=%s, hi_sync_id=%s, codec_in_use=%s, audio_running=%i",
+ address.ToStringForLogging().c_str(),
+ loghex(hearingDevice->hi_sync_id).c_str(),
+ loghex(codec_in_use).c_str(), audio_running);
StartSendingAudio(*hearingDevice);
@@ -1018,7 +1021,7 @@
}
void StartSendingAudio(const HearingDevice& hearingDevice) {
- VLOG(0) << __func__ << ": device=" << hearingDevice.address;
+ LOG_DEBUG("device=%s", hearingDevice.address.ToStringForLogging().c_str());
if (encoder_state_left == nullptr) {
encoder_state_init();
@@ -1048,9 +1051,9 @@
CHECK(stop_audio_ticks) << "stop_audio_ticks is empty";
if (!audio_running) {
- LOG(WARNING) << __func__ << ": Unexpected audio suspend";
+ LOG_WARN("Unexpected audio suspend");
} else {
- LOG(INFO) << __func__ << ": audio_running=" << audio_running;
+ LOG_INFO("audio_running=%i", audio_running);
}
audio_running = false;
stop_audio_ticks();
@@ -1060,11 +1063,11 @@
if (!device.accepting_audio) continue;
if (!device.playback_started) {
- LOG(WARNING) << __func__
- << ": Playback not started, skip send Stop cmd, device="
- << device.address;
+ LOG_WARN("Playback not started, skip send Stop cmd, device=%s",
+ device.address.ToStringForLogging().c_str());
} else {
- LOG(INFO) << __func__ << ": send Stop cmd, device=" << device.address;
+ LOG_INFO("send Stop cmd, device=%s",
+ device.address.ToStringForLogging().c_str());
device.playback_started = false;
device.command_acked = false;
BtaGattQueue::WriteCharacteristic(device.conn_id,
@@ -1078,9 +1081,9 @@
CHECK(start_audio_ticks) << "start_audio_ticks is empty";
if (audio_running) {
- LOG(ERROR) << __func__ << ": Unexpected Audio Resume";
+ LOG_ERROR("Unexpected Audio Resume");
} else {
- LOG(INFO) << __func__ << ": audio_running=" << audio_running;
+ LOG_INFO("audio_running=%i", audio_running);
}
for (auto& device : hearingDevices.devices) {
@@ -1090,8 +1093,7 @@
}
if (!audio_running) {
- LOG(INFO) << __func__ << ": No device (0/" << GetDeviceCount()
- << ") ready to start";
+ LOG_INFO("No device (0/%d) ready to start", GetDeviceCount());
return;
}
@@ -1119,8 +1121,8 @@
}
void SendEnableServiceChangedInd(HearingDevice* device) {
- VLOG(2) << __func__ << " Enable " << device->address
- << "service changed ind.";
+ LOG_DEBUG("Enable service changed ind.%s",
+ device->address.ToStringForLogging().c_str());
std::vector<uint8_t> value(2);
uint8_t* ptr = value.data();
UINT16_TO_STREAM(ptr, GATT_CHAR_CLIENT_CONFIG_INDICTION);
@@ -1136,13 +1138,11 @@
if (!audio_running) {
if (!device->playback_started) {
- LOG(INFO) << __func__
- << ": Skip Send Start since audio is not running, device="
- << device->address;
+ LOG_INFO("Skip Send Start since audio is not running, device=%s",
+ device->address.ToStringForLogging().c_str());
} else {
- LOG(ERROR) << __func__
- << ": Audio not running but Playback has started, device="
- << device->address;
+ LOG_ERROR("Audio not running but Playback has started, device=%s",
+ device->address.ToStringForLogging().c_str());
}
return;
}
@@ -1150,15 +1150,16 @@
if (current_volume == VOLUME_UNKNOWN) start[3] = (uint8_t)VOLUME_MIN;
if (device->playback_started) {
- LOG(ERROR) << __func__
- << ": Playback already started, skip send Start cmd, device="
- << device->address;
+ LOG_ERROR("Playback already started, skip send Start cmd, device=%s",
+ device->address.ToStringForLogging().c_str());
} else {
start[4] = GetOtherSideStreamStatus(device);
- LOG(INFO) << __func__ << ": send Start cmd, volume=" << loghex(start[3])
- << ", audio type=" << loghex(start[2])
- << ", device=" << device->address
- << ", other side streaming=" << loghex(start[4]);
+ LOG_INFO(
+ "send Start cmd, volume=%s, audio type=%s, device=%s, other side "
+ "streaming=%s",
+ loghex(start[3]).c_str(), loghex(start[2]).c_str(),
+ device->address.ToStringForLogging().c_str(),
+ loghex(start[4]).c_str());
device->command_acked = false;
BtaGattQueue::WriteCharacteristic(
device->conn_id, device->audio_control_point_handle, start,
@@ -1171,12 +1172,12 @@
uint16_t len, const uint8_t* value,
void* data) {
if (status != GATT_SUCCESS) {
- LOG(ERROR) << __func__ << ": handle=" << handle << ", conn_id=" << conn_id
- << ", status=" << loghex(static_cast<uint8_t>(status));
+ LOG_ERROR("handle=%u, conn_id=%u, status=%s", handle, conn_id,
+ loghex(static_cast<uint8_t>(status)).c_str());
return;
}
if (!instance) {
- LOG(ERROR) << __func__ << ": instance is null.";
+ LOG_ERROR("instance is null");
return;
}
instance->StartAudioCtrlCallback(conn_id);
@@ -1185,10 +1186,10 @@
void StartAudioCtrlCallback(uint16_t conn_id) {
HearingDevice* hearingDevice = hearingDevices.FindByConnId(conn_id);
if (!hearingDevice) {
- LOG(ERROR) << "Skipping unknown device, conn_id=" << loghex(conn_id);
+ LOG_ERROR("Skipping unknown device, conn_id=%s", loghex(conn_id).c_str());
return;
}
- LOG(INFO) << __func__ << ": device: " << hearingDevice->address;
+ LOG_INFO("device: %s", hearingDevice->address.ToStringForLogging().c_str());
hearingDevice->playback_started = true;
}
@@ -1203,7 +1204,7 @@
bool NeedToDropPacket(HearingDevice* target_side, HearingDevice* other_side) {
// Just drop packet if the other side does not exist.
if (!other_side) {
- VLOG(2) << __func__ << ": other side not connected to profile";
+ LOG_DEBUG("other side not connected to profile");
return true;
}
@@ -1212,14 +1213,14 @@
uint16_t target_current_credit = L2CA_GetPeerLECocCredit(
target_side->address, GAP_ConnGetL2CAPCid(target_side->gap_handle));
if (target_current_credit == L2CAP_LE_CREDIT_MAX) {
- LOG(ERROR) << __func__ << ": Get target side credit value fail.";
+ LOG_ERROR("Get target side credit value fail.");
return true;
}
uint16_t other_current_credit = L2CA_GetPeerLECocCredit(
other_side->address, GAP_ConnGetL2CAPCid(other_side->gap_handle));
if (other_current_credit == L2CAP_LE_CREDIT_MAX) {
- LOG(ERROR) << __func__ << ": Get other side credit value fail.";
+ LOG_ERROR("Get other side credit value fail.");
return true;
}
@@ -1228,24 +1229,23 @@
} else {
diff_credit = other_current_credit - target_current_credit;
}
- VLOG(2) << __func__ << ": Target(" << target_side->address
- << ") Credit: " << target_current_credit << ", Other("
- << other_side->address << ") Credit: " << other_current_credit
- << ", Init Credit: " << init_credit;
+ LOG_DEBUG("Target(%s) Credit: %u, Other(%s) Credit: %u, Init Credit: %u",
+ target_side->address.ToStringForLogging().c_str(),
+ target_current_credit,
+ other_side->address.ToStringForLogging().c_str(),
+ other_current_credit, init_credit);
return diff_credit < (init_credit / 2 - 1);
}
void OnAudioDataReady(const std::vector<uint8_t>& data) {
/* For now we assume data comes in as 16bit per sample 16kHz PCM stereo */
- DVLOG(2) << __func__;
-
bool need_drop = false;
int num_samples =
data.size() / (2 /*bytes_per_sample*/ * 2 /*number of channels*/);
// The G.722 codec accept only even number of samples for encoding
if (num_samples % 2 != 0)
- LOG(FATAL) << "num_samples is not even: " << num_samples;
+ LOG_ALWAYS_FATAL("num_samples is not even: %d", num_samples);
// TODO: we should cache left/right and current state, instad of recomputing
// it for each packet, 100 times a second.
@@ -1261,8 +1261,7 @@
}
if (left == nullptr && right == nullptr) {
- LOG(WARNING) << __func__ << ": No more (0/" << GetDeviceCount()
- << ") devices ready";
+ LOG_WARN("No more (0/%d) devices ready", GetDeviceCount());
DoDisconnectAudioStop();
return;
}
@@ -1318,13 +1317,15 @@
// Compare the two sides LE CoC credit value to confirm need to drop or
// skip audio packet.
if (NeedToDropPacket(left, right)) {
- LOG(INFO) << left->address << " triggers dropping, "
- << packets_in_chans << " packets in channel";
+ LOG_INFO("%s triggers dropping, %u packets in channel",
+ left->address.ToStringForLogging().c_str(),
+ packets_in_chans);
need_drop = true;
left->audio_stats.trigger_drop_count++;
} else {
- LOG(INFO) << left->address << " skipping " << packets_in_chans
- << " packets";
+ LOG_INFO("%s skipping %u packets",
+ left->address.ToStringForLogging().c_str(),
+ packets_in_chans);
left->audio_stats.packet_flush_count += packets_in_chans;
left->audio_stats.frame_flush_count++;
L2CA_FlushChannel(cid, 0xffff);
@@ -1350,13 +1351,15 @@
// Compare the two sides LE CoC credit value to confirm need to drop or
// skip audio packet.
if (NeedToDropPacket(right, left)) {
- LOG(INFO) << right->address << " triggers dropping, "
- << packets_in_chans << " packets in channel";
+ LOG_INFO("%s triggers dropping, %u packets in channel",
+ right->address.ToStringForLogging().c_str(),
+ packets_in_chans);
need_drop = true;
right->audio_stats.trigger_drop_count++;
} else {
- LOG(INFO) << right->address << " skipping " << packets_in_chans
- << " packets";
+ LOG_INFO("%s skipping %u packets",
+ right->address.ToStringForLogging().c_str(),
+ packets_in_chans);
right->audio_stats.packet_flush_count += packets_in_chans;
right->audio_stats.frame_flush_count++;
L2CA_FlushChannel(cid, 0xffff);
@@ -1400,10 +1403,9 @@
void SendAudio(uint8_t* encoded_data, uint16_t packet_size,
HearingDevice* hearingAid) {
if (!hearingAid->playback_started || !hearingAid->command_acked) {
- VLOG(2) << __func__
- << ": Playback stalled, device=" << hearingAid->address
- << ", cmd send=" << hearingAid->playback_started
- << ", cmd acked=" << hearingAid->command_acked;
+ LOG_DEBUG("Playback stalled, device=%s,cmd send=%i, cmd acked=%i",
+ hearingAid->address.ToStringForLogging().c_str(),
+ hearingAid->playback_started, hearingAid->command_acked);
return;
}
@@ -1413,19 +1415,20 @@
p++;
memcpy(p, encoded_data, packet_size);
- DVLOG(2) << hearingAid->address << " : " << base::HexEncode(p, packet_size);
+ LOG_DEBUG("%s : %s", hearingAid->address.ToStringForLogging().c_str(),
+ base::HexEncode(p, packet_size).c_str());
uint16_t result = GAP_ConnWriteData(hearingAid->gap_handle, audio_packet);
if (result != BT_PASS) {
- LOG(ERROR) << " Error sending data: " << loghex(result);
+ LOG_ERROR("Error sending data: %s", loghex(result).c_str());
}
}
void GapCallback(uint16_t gap_handle, uint16_t event, tGAP_CB_DATA* data) {
HearingDevice* hearingDevice = hearingDevices.FindByGapHandle(gap_handle);
if (!hearingDevice) {
- LOG(INFO) << "Skipping unknown device, gap_handle=" << gap_handle;
+ LOG_INFO("Skipping unknown device, gap_handle=%u", gap_handle);
return;
}
@@ -1437,12 +1440,13 @@
init_credit =
L2CA_GetPeerLECocCredit(address, GAP_ConnGetL2CAPCid(gap_handle));
- LOG(INFO) << "GAP_EVT_CONN_OPENED " << address << ", tx_mtu=" << tx_mtu
- << ", init_credit=" << init_credit;
+ LOG_INFO("GAP_EVT_CONN_OPENED %s, tx_mtu=%u, init_credit=%u",
+ address.ToStringForLogging().c_str(), tx_mtu, init_credit);
HearingDevice* hearingDevice = hearingDevices.FindByAddress(address);
if (!hearingDevice) {
- LOG(INFO) << "Skipping unknown device" << address;
+ LOG_INFO("Skipping unknown device %s",
+ address.ToStringForLogging().c_str());
return;
}
hearingDevice->gap_opened = true;
@@ -1453,10 +1457,11 @@
}
case GAP_EVT_CONN_CLOSED:
- LOG(INFO) << __func__
- << ": GAP_EVT_CONN_CLOSED: " << hearingDevice->address
- << ", playback_started=" << hearingDevice->playback_started
- << ", accepting_audio=" << hearingDevice->accepting_audio;
+ LOG_INFO(
+ "GAP_EVT_CONN_CLOSED: %s, playback_started=%i, "
+ "accepting_audio=%i",
+ hearingDevice->address.ToStringForLogging().c_str(),
+ hearingDevice->playback_started, hearingDevice->accepting_audio);
if (!hearingDevice->accepting_audio) {
/* Disconnect connection when data channel is not available */
BTA_GATTC_Close(hearingDevice->conn_id);
@@ -1471,7 +1476,7 @@
}
break;
case GAP_EVT_CONN_DATA_AVAIL: {
- DVLOG(2) << "GAP_EVT_CONN_DATA_AVAIL";
+ LOG_DEBUG("GAP_EVT_CONN_DATA_AVAIL");
// only data we receive back from hearing aids are some stats, not
// really important, but useful now for debugging.
@@ -1484,28 +1489,28 @@
GAP_ConnReadData(gap_handle, buffer.data(), buffer.size(), &bytes_read);
if (bytes_read < 4) {
- LOG(WARNING) << " Wrong data length";
+ LOG_WARN("Wrong data length");
return;
}
uint8_t* p = buffer.data();
- DVLOG(1) << "stats from the hearing aid:";
+ LOG_DEBUG("stats from the hearing aid:");
for (size_t i = 0; i + 4 <= buffer.size(); i += 4) {
uint16_t event_counter, frame_index;
STREAM_TO_UINT16(event_counter, p);
STREAM_TO_UINT16(frame_index, p);
- DVLOG(1) << "event_counter=" << event_counter
- << " frame_index: " << frame_index;
+ LOG_DEBUG("event_counter=%u frame_index: %u", event_counter,
+ frame_index);
}
break;
}
case GAP_EVT_TX_EMPTY:
- DVLOG(2) << "GAP_EVT_TX_EMPTY";
+ LOG_DEBUG("GAP_EVT_TX_EMPTY");
break;
case GAP_EVT_CONN_CONGESTED:
- DVLOG(2) << "GAP_EVT_CONN_CONGESTED";
+ LOG_DEBUG("GAP_EVT_CONN_CONGESTED");
// TODO: make it into function
HearingAidAudioSource::Stop();
@@ -1515,7 +1520,7 @@
// encoder_state_right = nulllptr;
break;
case GAP_EVT_CONN_UNCONGESTED:
- DVLOG(2) << "GAP_EVT_CONN_UNCONGESTED";
+ LOG_DEBUG("GAP_EVT_CONN_UNCONGESTED");
break;
}
}
@@ -1544,8 +1549,8 @@
char temptime[20];
struct tm* tstamp = localtime(&rssi_logs.timestamp.tv_sec);
if (!strftime(temptime, sizeof(temptime), "%H:%M:%S", tstamp)) {
- LOG(ERROR) << __func__ << ": strftime fails. tm_sec=" << tstamp->tm_sec << ", tm_min=" << tstamp->tm_min
- << ", tm_hour=" << tstamp->tm_hour;
+ LOG_ERROR("strftime fails. tm_sec=%d, tm_min=%d, tm_hour=%d",
+ tstamp->tm_sec, tstamp->tm_min, tstamp->tm_hour);
strlcpy(temptime, "UNKNOWN TIME", sizeof(temptime));
}
snprintf(eventtime, sizeof(eventtime), "%s.%03ld", temptime, rssi_logs.timestamp.tv_nsec / 1000000);
@@ -1586,22 +1591,22 @@
dprintf(fd, "%s", stream.str().c_str());
}
- void Disconnect(const RawAddress& address) override {
- DVLOG(2) << __func__;
+ void Disconnect(const RawAddress& address) {
HearingDevice* hearingDevice = hearingDevices.FindByAddress(address);
if (!hearingDevice) {
- LOG(INFO) << "Device not connected to profile" << address;
+ LOG_INFO("Device not connected to profile %s",
+ address.ToStringForLogging().c_str());
return;
}
- VLOG(2) << __func__ << ": " << address;
+ LOG_DEBUG("%s", address.ToStringForLogging().c_str());
bool connected = hearingDevice->accepting_audio;
bool connecting_by_user = hearingDevice->connecting_actively;
- LOG(INFO) << __func__ << ": " << hearingDevice->address
- << ", playback_started=" << hearingDevice->playback_started
- << ", accepting_audio=" << hearingDevice->accepting_audio;
+ LOG_INFO("%s, playback_started=%i, accepting_audio=%i",
+ hearingDevice->address.ToStringForLogging().c_str(),
+ hearingDevice->playback_started, hearingDevice->accepting_audio);
if (hearingDevice->connecting_actively) {
// cancel pending direct connect
@@ -1634,8 +1639,7 @@
for (const auto& device : hearingDevices.devices) {
if (device.accepting_audio) return;
}
- LOG(INFO) << __func__ << ": No more (0/" << GetDeviceCount()
- << ") devices ready";
+ LOG_INFO("No more (0/%d) devices ready", GetDeviceCount());
DoDisconnectAudioStop();
}
@@ -1643,12 +1647,12 @@
RawAddress remote_bda) {
HearingDevice* hearingDevice = hearingDevices.FindByConnId(conn_id);
if (!hearingDevice) {
- VLOG(2) << "Skipping unknown device disconnect, conn_id="
- << loghex(conn_id);
+ LOG_DEBUG("Skipping unknown device disconnect, conn_id=%s",
+ loghex(conn_id).c_str());
return;
}
- VLOG(2) << __func__ << ": conn_id=" << loghex(conn_id)
- << ", remote_bda=" << remote_bda;
+ LOG_DEBUG("conn_id=%s, remote_bda=%s", loghex(conn_id).c_str(),
+ remote_bda.ToStringForLogging().c_str());
// Inform the other side (if any) of this disconnection
std::vector<uint8_t> inform_disconn_state(
@@ -1667,16 +1671,15 @@
for (const auto& device : hearingDevices.devices) {
if (device.accepting_audio) return;
}
- LOG(INFO) << __func__ << ": No more (0/" << GetDeviceCount()
- << ") devices ready";
+ LOG_INFO("No more (0/%d) devices ready", GetDeviceCount());
DoDisconnectAudioStop();
}
void DoDisconnectCleanUp(HearingDevice* hearingDevice) {
if (hearingDevice->connection_update_status != COMPLETED) {
- LOG(INFO) << __func__ << ": connection update not completed. Current="
- << hearingDevice->connection_update_status
- << ", device=" << hearingDevice->address;
+ LOG_INFO("connection update not completed. Current=%u, device=%s",
+ hearingDevice->connection_update_status,
+ hearingDevice->address.ToStringForLogging().c_str());
if (hearingDevice->connection_update_status == STARTED) {
OnConnectionUpdateComplete(hearingDevice->conn_id, NULL);
@@ -1697,8 +1700,9 @@
}
hearingDevice->accepting_audio = false;
- LOG(INFO) << __func__ << ": device=" << hearingDevice->address
- << ", playback_started=" << hearingDevice->playback_started;
+ LOG_INFO("device=%s, playback_started=%i",
+ hearingDevice->address.ToStringForLogging().c_str(),
+ hearingDevice->playback_started);
hearingDevice->playback_started = false;
hearingDevice->command_acked = false;
}
@@ -1710,8 +1714,8 @@
current_volume = VOLUME_UNKNOWN;
}
- void SetVolume(int8_t volume) override {
- VLOG(2) << __func__ << ": " << +volume;
+ void SetVolume(int8_t volume) {
+ LOG_DEBUG("%d", volume);
current_volume = volume;
for (HearingDevice& device : hearingDevices.devices) {
if (!device.accepting_audio) continue;
@@ -1754,7 +1758,7 @@
const gatt::Service* service) {
HearingDevice* hearingDevice = hearingDevices.FindByConnId(conn_id);
if (!hearingDevice) {
- DVLOG(2) << "Skipping unknown device, conn_id=" << loghex(conn_id);
+ LOG_DEBUG("Skipping unknown device, conn_id=%s", loghex(conn_id).c_str());
return;
}
for (const gatt::Characteristic& charac : service->characteristics) {
@@ -1762,12 +1766,11 @@
hearingDevice->service_changed_ccc_handle =
find_ccc_handle(conn_id, charac.value_handle);
if (!hearingDevice->service_changed_ccc_handle) {
- LOG(ERROR) << __func__
- << ": cannot find service changed CCC descriptor";
+ LOG_ERROR("cannot find service changed CCC descriptor");
continue;
}
- LOG(INFO) << __func__ << " service_changed_ccc="
- << loghex(hearingDevice->service_changed_ccc_handle);
+ LOG_INFO("service_changed_ccc=%s",
+ loghex(hearingDevice->service_changed_ccc_handle).c_str());
break;
}
}
@@ -1780,7 +1783,7 @@
BTA_GATTC_GetCharacteristic(conn_id, char_handle);
if (!p_char) {
- LOG(WARNING) << __func__ << ": No such characteristic: " << char_handle;
+ LOG_WARN("No such characteristic: %u", char_handle);
return 0;
}
@@ -1795,14 +1798,14 @@
void send_state_change(HearingDevice* device, std::vector<uint8_t> payload) {
if (device->conn_id != 0) {
if (device->service_changed_rcvd) {
- LOG(INFO)
- << __func__
- << ": service discover is in progress, skip send State Change cmd.";
+ LOG_INFO(
+ "service discover is in progress, skip send State Change cmd.");
return;
}
// Send the data packet
- LOG(INFO) << __func__ << ": Send State Change. device=" << device->address
- << ", status=" << loghex(payload[1]);
+ LOG_INFO("Send State Change. device=%s, status=%s",
+ device->address.ToStringForLogging().c_str(),
+ loghex(payload[1]).c_str());
BtaGattQueue::WriteCharacteristic(
device->conn_id, device->audio_control_point_handle, payload,
GATT_WRITE_NO_RSP, nullptr, nullptr);
@@ -1825,7 +1828,7 @@
device->num_intervals_since_last_rssi_read++;
if (device->num_intervals_since_last_rssi_read >= PERIOD_TO_READ_RSSI_IN_INTERVALS) {
device->num_intervals_since_last_rssi_read = 0;
- VLOG(1) << __func__ << ": device=" << device->address;
+ LOG_DEBUG("device=%s", device->address.ToStringForLogging().c_str());
BTM_ReadRSSI(device->address, read_rssi_cb);
}
}
@@ -1843,7 +1846,7 @@
}
void hearingaid_gattc_callback(tBTA_GATTC_EVT event, tBTA_GATTC* p_data) {
- VLOG(2) << __func__ << " event = " << +event;
+ LOG_DEBUG("event = %u", event);
if (p_data == nullptr) return;
@@ -1874,9 +1877,8 @@
case BTA_GATTC_NOTIF_EVT:
if (!instance) return;
if (!p_data->notify.is_notify || p_data->notify.len > GATT_MAX_ATTR_LEN) {
- LOG(ERROR) << __func__ << ": rejected BTA_GATTC_NOTIF_EVT. is_notify="
- << p_data->notify.is_notify
- << ", len=" << p_data->notify.len;
+ LOG_ERROR("rejected BTA_GATTC_NOTIF_EVT. is_notify=%i, len=%u",
+ p_data->notify.is_notify, p_data->notify.len);
break;
}
instance->OnNotificationEvent(p_data->notify.conn_id,
@@ -1945,7 +1947,8 @@
void HearingAid::Initialize(
bluetooth::hearing_aid::HearingAidCallbacks* callbacks, Closure initCb) {
if (instance) {
- LOG(ERROR) << "Already initialized!";
+ LOG_ERROR("Already initialized!");
+ return;
}
audioReceiver = &audioReceiverImpl;
@@ -1955,15 +1958,42 @@
bool HearingAid::IsHearingAidRunning() { return instance; }
-HearingAid* HearingAid::Get() {
- CHECK(instance);
- return instance;
-};
+void HearingAid::Connect(const RawAddress& address) {
+ if (!instance) {
+ LOG_ERROR("Hearing Aid instance is not available");
+ return;
+ }
+ instance->Connect(address);
+}
+
+void HearingAid::Disconnect(const RawAddress& address) {
+ if (!instance) {
+ LOG_ERROR("Hearing Aid instance is not available");
+ return;
+ }
+ instance->Disconnect(address);
+}
+
+void HearingAid::AddToAcceptlist(const RawAddress& address) {
+ if (!instance) {
+ LOG_ERROR("Hearing Aid instance is not available");
+ return;
+ }
+ instance->AddToAcceptlist(address);
+}
+
+void HearingAid::SetVolume(int8_t volume) {
+ if (!instance) {
+ LOG_ERROR("Hearing Aid instance is not available");
+ return;
+ }
+ instance->SetVolume(volume);
+}
void HearingAid::AddFromStorage(const HearingDevice& dev_info,
uint16_t is_acceptlisted) {
if (!instance) {
- LOG(ERROR) << "Not initialized yet";
+ LOG_ERROR("Not initialized yet");
}
instance->AddFromStorage(dev_info, is_acceptlisted);
@@ -1971,7 +2001,7 @@
int HearingAid::GetDeviceCount() {
if (!instance) {
- LOG(INFO) << __func__ << ": Not initialized yet";
+ LOG_INFO("Not initialized yet");
return 0;
}
diff --git a/system/bta/hearing_aid/hearing_aid_audio_source.cc b/system/bta/hearing_aid/hearing_aid_audio_source.cc
index c7dc6ae..e347f87 100644
--- a/system/bta/hearing_aid/hearing_aid_audio_source.cc
+++ b/system/bta/hearing_aid/hearing_aid_audio_source.cc
@@ -18,6 +18,7 @@
#include <base/files/file_util.h>
#include <base/logging.h>
+
#include <cstdint>
#include <memory>
#include <sstream>
@@ -28,6 +29,7 @@
#include "bta/include/bta_hearing_aid_api.h"
#include "common/repeating_timer.h"
#include "common/time_util.h"
+#include "osi/include/log.h"
#include "osi/include/wakelock.h"
#include "stack/include/btu.h" // get_main_thread
#include "udrv/include/uipc.h"
@@ -98,7 +100,7 @@
bytes_per_tick);
}
- VLOG(2) << "bytes_read: " << bytes_read;
+ LOG_DEBUG("bytes_read: %u", bytes_read);
if (bytes_read < bytes_per_tick) {
stats.media_read_total_underflow_bytes += bytes_per_tick - bytes_read;
stats.media_read_total_underflow_count++;
@@ -115,14 +117,14 @@
void hearing_aid_send_ack(tHEARING_AID_CTRL_ACK status) {
uint8_t ack = status;
- DVLOG(2) << "Hearing Aid audio ctrl ack: " << status;
+ LOG_DEBUG("Hearing Aid audio ctrl ack: %u", status);
UIPC_Send(*uipc_hearing_aid, UIPC_CH_ID_AV_CTRL, 0, &ack, sizeof(ack));
}
void start_audio_ticks() {
if (data_interval_ms != HA_INTERVAL_10_MS &&
data_interval_ms != HA_INTERVAL_20_MS) {
- LOG(FATAL) << " Unsupported data interval: " << data_interval_ms;
+ LOG_ALWAYS_FATAL("Unsupported data interval: %d", data_interval_ms);
}
wakelock_acquire();
@@ -133,20 +135,20 @@
#else
base::Milliseconds(data_interval_ms));
#endif
- LOG(INFO) << __func__ << ": running with data interval: " << data_interval_ms;
+ LOG_INFO("running with data interval: %d", data_interval_ms);
}
void stop_audio_ticks() {
- LOG(INFO) << __func__ << ": stopped";
+ LOG_INFO("stopped");
audio_timer.CancelAndWait();
wakelock_release();
}
void hearing_aid_data_cb(tUIPC_CH_ID, tUIPC_EVENT event) {
- DVLOG(2) << "Hearing Aid audio data event: " << event;
+ LOG_DEBUG("Hearing Aid audio data event: %u", event);
switch (event) {
case UIPC_OPEN_EVT:
- LOG(INFO) << __func__ << ": UIPC_OPEN_EVT";
+ LOG_INFO("UIPC_OPEN_EVT");
/*
* Read directly from media task from here on (keep callback for
* connection events.
@@ -159,12 +161,12 @@
do_in_main_thread(FROM_HERE, base::BindOnce(start_audio_ticks));
break;
case UIPC_CLOSE_EVT:
- LOG(INFO) << __func__ << ": UIPC_CLOSE_EVT";
+ LOG_INFO("UIPC_CLOSE_EVT");
hearing_aid_send_ack(HEARING_AID_CTRL_ACK_SUCCESS);
do_in_main_thread(FROM_HERE, base::BindOnce(stop_audio_ticks));
break;
default:
- LOG(ERROR) << "Hearing Aid audio data event not recognized:" << event;
+ LOG_ERROR("Hearing Aid audio data event not recognized: %u", event);
}
}
@@ -178,12 +180,12 @@
/* detach on ctrl channel means audioflinger process was terminated */
if (n == 0) {
- LOG(WARNING) << __func__ << "CTRL CH DETACHED";
+ LOG_WARN("CTRL CH DETACHED");
UIPC_Close(*uipc_hearing_aid, UIPC_CH_ID_AV_CTRL);
return;
}
- LOG(INFO) << __func__ << " " << audio_ha_hw_dump_ctrl_event(cmd);
+ LOG_INFO("%s", audio_ha_hw_dump_ctrl_event(cmd));
// a2dp_cmd_pending = cmd;
tHEARING_AID_CTRL_ACK ctrl_ack_status;
@@ -207,7 +209,9 @@
case HEARING_AID_CTRL_CMD_STOP:
if (!hearing_aid_on_suspend_req()) {
- LOG(INFO) << __func__ << ":HEARING_AID_CTRL_CMD_STOP: hearing_aid_on_suspend_req() errs, but ignored.";
+ LOG_INFO(
+ "HEARING_AID_CTRL_CMD_STOP: hearing_aid_on_suspend_req() errs, but "
+ "ignored.");
}
hearing_aid_send_ack(HEARING_AID_CTRL_ACK_SUCCESS);
break;
@@ -230,7 +234,7 @@
codec_config.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_24000;
codec_capability.sample_rate = BTAV_A2DP_CODEC_SAMPLE_RATE_24000;
} else {
- LOG(FATAL) << "unsupported sample rate: " << sample_rate;
+ LOG_ALWAYS_FATAL("unsupported sample rate: %d", sample_rate);
}
codec_config.bits_per_sample = BTAV_A2DP_CODEC_BITS_PER_SAMPLE_16;
@@ -278,15 +282,14 @@
reinterpret_cast<uint8_t*>(&codec_config.sample_rate),
sizeof(btav_a2dp_codec_sample_rate_t)) !=
sizeof(btav_a2dp_codec_sample_rate_t)) {
- LOG(ERROR) << __func__ << "Error reading sample rate from audio HAL";
+ LOG_ERROR("Error reading sample rate from audio HAL");
break;
}
if (UIPC_Read(*uipc_hearing_aid, UIPC_CH_ID_AV_CTRL,
reinterpret_cast<uint8_t*>(&codec_config.bits_per_sample),
sizeof(btav_a2dp_codec_bits_per_sample_t)) !=
sizeof(btav_a2dp_codec_bits_per_sample_t)) {
- LOG(ERROR) << __func__
- << "Error reading bits per sample from audio HAL";
+ LOG_ERROR("Error reading bits per sample from audio HAL");
break;
}
@@ -294,29 +297,28 @@
reinterpret_cast<uint8_t*>(&codec_config.channel_mode),
sizeof(btav_a2dp_codec_channel_mode_t)) !=
sizeof(btav_a2dp_codec_channel_mode_t)) {
- LOG(ERROR) << __func__ << "Error reading channel mode from audio HAL";
+ LOG_ERROR("Error reading channel mode from audio HAL");
break;
}
- LOG(INFO) << __func__ << " HEARING_AID_CTRL_SET_OUTPUT_AUDIO_CONFIG: "
- << "sample_rate=" << codec_config.sample_rate
- << "bits_per_sample=" << codec_config.bits_per_sample
- << "channel_mode=" << codec_config.channel_mode;
+ LOG_INFO(
+ "HEARING_AID_CTRL_SET_OUTPUT_AUDIO_CONFIG: sample_rate=%u, "
+ "bits_per_sample=%u,channel_mode=%u",
+ codec_config.sample_rate, codec_config.bits_per_sample,
+ codec_config.channel_mode);
break;
}
default:
- LOG(ERROR) << __func__ << "UNSUPPORTED CMD: " << cmd;
+ LOG_ERROR("UNSUPPORTED CMD: %u", cmd);
hearing_aid_send_ack(HEARING_AID_CTRL_ACK_FAILURE);
break;
}
- LOG(INFO) << __func__
- << " a2dp-ctrl-cmd : " << audio_ha_hw_dump_ctrl_event(cmd)
- << " DONE";
+ LOG_INFO("a2dp-ctrl-cmd : %s DONE", audio_ha_hw_dump_ctrl_event(cmd));
}
void hearing_aid_ctrl_cb(tUIPC_CH_ID, tUIPC_EVENT event) {
- VLOG(2) << "Hearing Aid audio ctrl event: " << event;
+ LOG_DEBUG("Hearing Aid audio ctrl event: %u", event);
switch (event) {
case UIPC_OPEN_EVT:
break;
@@ -331,14 +333,13 @@
hearing_aid_recv_ctrl_data();
break;
default:
- LOG(ERROR) << "Hearing Aid audio ctrl unrecognized event: " << event;
+ LOG_ERROR("Hearing Aid audio ctrl unrecognized event: %u", event);
}
}
bool hearing_aid_on_resume_req(bool start_media_task) {
if (localAudioReceiver == nullptr) {
- LOG(ERROR) << __func__
- << ": HEARING_AID_CTRL_CMD_START: audio receiver not started";
+ LOG_ERROR("HEARING_AID_CTRL_CMD_START: audio receiver not started");
return false;
}
bt_status_t status;
@@ -349,7 +350,7 @@
start_audio_ticks));
} else {
auto start_dummy_ticks = []() {
- LOG(INFO) << "start_audio_ticks: waiting for data path opened";
+ LOG_INFO("start_audio_ticks: waiting for data path opened");
};
status = do_in_main_thread(
FROM_HERE, base::BindOnce(&HearingAidAudioReceiver::OnAudioResume,
@@ -357,9 +358,7 @@
start_dummy_ticks));
}
if (status != BT_STATUS_SUCCESS) {
- LOG(ERROR) << __func__
- << ": HEARING_AID_CTRL_CMD_START: do_in_main_thread err="
- << status;
+ LOG_ERROR("HEARING_AID_CTRL_CMD_START: do_in_main_thread err=%u", status);
return false;
}
return true;
@@ -367,8 +366,7 @@
bool hearing_aid_on_suspend_req() {
if (localAudioReceiver == nullptr) {
- LOG(ERROR) << __func__
- << ": HEARING_AID_CTRL_CMD_SUSPEND: audio receiver not started";
+ LOG_ERROR("HEARING_AID_CTRL_CMD_SUSPEND: audio receiver not started");
return false;
}
bt_status_t status = do_in_main_thread(
@@ -376,9 +374,7 @@
base::BindOnce(&HearingAidAudioReceiver::OnAudioSuspend,
base::Unretained(localAudioReceiver), stop_audio_ticks));
if (status != BT_STATUS_SUCCESS) {
- LOG(ERROR) << __func__
- << ": HEARING_AID_CTRL_CMD_SUSPEND: do_in_main_thread err="
- << status;
+ LOG_ERROR("HEARING_AID_CTRL_CMD_SUSPEND: do_in_main_thread err=%u", status);
return false;
}
return true;
@@ -388,7 +384,7 @@
void HearingAidAudioSource::Start(const CodecConfiguration& codecConfiguration,
HearingAidAudioReceiver* audioReceiver,
uint16_t remote_delay_ms) {
- LOG(INFO) << __func__ << ": Hearing Aid Source Open";
+ LOG_INFO("Hearing Aid Source Open");
bit_rate = codecConfiguration.bit_rate;
sample_rate = codecConfiguration.sample_rate;
@@ -404,7 +400,7 @@
}
void HearingAidAudioSource::Stop() {
- LOG(INFO) << __func__ << ": Hearing Aid Source Close";
+ LOG_INFO("Hearing Aid Source Close");
localAudioReceiver = nullptr;
if (bluetooth::audio::hearing_aid::is_hal_enabled()) {
@@ -420,7 +416,7 @@
.on_suspend_ = hearing_aid_on_suspend_req,
};
if (!bluetooth::audio::hearing_aid::init(stream_cb, get_main_thread())) {
- LOG(WARNING) << __func__ << ": Using legacy HAL";
+ LOG_WARN("Using legacy HAL");
uipc_hearing_aid = UIPC_Init();
UIPC_Open(*uipc_hearing_aid, UIPC_CH_ID_AV_CTRL, hearing_aid_ctrl_cb, HEARING_AID_CTRL_PATH);
}
diff --git a/system/bta/hf_client/bta_hf_client_api.cc b/system/bta/hf_client/bta_hf_client_api.cc
index 15d57fe..d64b5ed 100644
--- a/system/bta/hf_client/bta_hf_client_api.cc
+++ b/system/bta/hf_client/bta_hf_client_api.cc
@@ -28,6 +28,10 @@
#include <cstdint>
+#ifdef OS_ANDROID
+#include <hfp.sysprop.h>
+#endif
+
#include "bt_trace.h" // Legacy trace logging
#include "bta/hf_client/bta_hf_client_int.h"
#include "bta/sys/bta_sys.h"
@@ -80,17 +84,17 @@
* Description Opens up a RF connection to the remote device and
* subsequently set it up for a HF SLC
*
- * Returns void
+ * Returns bt_status_t
*
******************************************************************************/
-void BTA_HfClientOpen(const RawAddress& bd_addr, uint16_t* p_handle) {
+bt_status_t BTA_HfClientOpen(const RawAddress& bd_addr, uint16_t* p_handle) {
APPL_TRACE_DEBUG("%s", __func__);
tBTA_HF_CLIENT_API_OPEN* p_buf =
(tBTA_HF_CLIENT_API_OPEN*)osi_malloc(sizeof(tBTA_HF_CLIENT_API_OPEN));
if (!bta_hf_client_allocate_handle(bd_addr, p_handle)) {
APPL_TRACE_ERROR("%s: could not allocate handle", __func__);
- return;
+ return BT_STATUS_FAIL;
}
p_buf->hdr.event = BTA_HF_CLIENT_API_OPEN_EVT;
@@ -98,6 +102,7 @@
p_buf->bd_addr = bd_addr;
bta_sys_sendmsg(p_buf);
+ return BT_STATUS_SUCCESS;
}
/*******************************************************************************
@@ -203,3 +208,29 @@
*
******************************************************************************/
void BTA_HfClientDumpStatistics(int fd) { bta_hf_client_dump_statistics(fd); }
+
+/*******************************************************************************
+ *
+ * function get_default_hf_client_features
+ *
+ * description return the hf_client features.
+ * value can be override via system property
+ *
+ * returns int
+ *
+ ******************************************************************************/
+int get_default_hf_client_features() {
+#define DEFAULT_BTIF_HF_CLIENT_FEATURES \
+ (BTA_HF_CLIENT_FEAT_ECNR | BTA_HF_CLIENT_FEAT_3WAY | \
+ BTA_HF_CLIENT_FEAT_CLI | BTA_HF_CLIENT_FEAT_VREC | BTA_HF_CLIENT_FEAT_VOL | \
+ BTA_HF_CLIENT_FEAT_ECS | BTA_HF_CLIENT_FEAT_ECC | BTA_HF_CLIENT_FEAT_CODEC)
+
+#ifdef OS_ANDROID
+ static const int features =
+ android::sysprop::bluetooth::Hfp::hf_client_features().value_or(
+ DEFAULT_BTIF_HF_CLIENT_FEATURES);
+ return features;
+#else
+ return DEFAULT_BTIF_HF_CLIENT_FEATURES;
+#endif
+}
diff --git a/system/bta/hf_client/bta_hf_client_at.cc b/system/bta/hf_client/bta_hf_client_at.cc
index bb9eec3..97fcb97 100644
--- a/system/bta/hf_client/bta_hf_client_at.cc
+++ b/system/bta/hf_client/bta_hf_client_at.cc
@@ -20,11 +20,11 @@
#define LOG_TAG "bt_hf_client"
#include "bt_trace.h" // Legacy trace logging
-
#include "bta/hf_client/bta_hf_client_int.h"
#include "osi/include/allocator.h"
#include "osi/include/compat.h"
#include "osi/include/log.h"
+#include "osi/include/properties.h"
#include "stack/include/port_api.h"
/* Uncomment to enable AT traffic dumping */
@@ -124,7 +124,7 @@
tBTA_HF_CLIENT_AT_QCMD* new_cmd =
(tBTA_HF_CLIENT_AT_QCMD*)osi_malloc(sizeof(tBTA_HF_CLIENT_AT_QCMD));
- APPL_TRACE_DEBUG("%s", __func__);
+ APPL_TRACE_DEBUG("%s: cmd:%d", __func__, (int)cmd);
new_cmd->cmd = cmd;
new_cmd->buf_len = buf_len;
@@ -169,7 +169,7 @@
static void bta_hf_client_send_at(tBTA_HF_CLIENT_CB* client_cb,
tBTA_HF_CLIENT_AT_CMD cmd, const char* buf,
uint16_t buf_len) {
- APPL_TRACE_DEBUG("%s", __func__);
+ APPL_TRACE_DEBUG("%s %d", __func__, cmd);
if ((client_cb->at_cb.current_cmd == BTA_HF_CLIENT_AT_NONE ||
!client_cb->svc_conn) &&
!alarm_is_scheduled(client_cb->at_cb.hold_timer)) {
@@ -197,6 +197,7 @@
return;
}
+ APPL_TRACE_DEBUG("%s: busy! queued: %d", __func__, cmd);
bta_hf_client_queue_at(client_cb, cmd, buf, buf_len);
}
@@ -240,7 +241,8 @@
******************************************************************************/
static void bta_hf_client_handle_ok(tBTA_HF_CLIENT_CB* client_cb) {
- APPL_TRACE_DEBUG("%s", __func__);
+ APPL_TRACE_DEBUG("%s: current_cmd:%d", __func__,
+ client_cb->at_cb.current_cmd);
bta_hf_client_stop_at_resp_timer(client_cb);
@@ -265,6 +267,9 @@
case BTA_HF_CLIENT_AT_NONE:
bta_hf_client_stop_at_hold_timer(client_cb);
break;
+ case BTA_HF_CLIENT_AT_ANDROID:
+ bta_hf_client_at_result(client_cb, BTA_HF_CLIENT_AT_RESULT_OK, 0);
+ break;
default:
if (client_cb->send_at_reply) {
bta_hf_client_at_result(client_cb, BTA_HF_CLIENT_AT_RESULT_OK, 0);
@@ -280,7 +285,8 @@
static void bta_hf_client_handle_error(tBTA_HF_CLIENT_CB* client_cb,
tBTA_HF_CLIENT_AT_RESULT_TYPE type,
uint16_t cme) {
- APPL_TRACE_DEBUG("%s: %u %u", __func__, type, cme);
+ APPL_TRACE_DEBUG("%s: type:%u cme:%u current_cmd:%d", __func__, type, cme,
+ client_cb->at_cb.current_cmd);
bta_hf_client_stop_at_resp_timer(client_cb);
@@ -301,6 +307,9 @@
client_cb->send_at_reply = true;
}
break;
+ case BTA_HF_CLIENT_AT_ANDROID:
+ bta_hf_client_at_result(client_cb, type, cme);
+ break;
default:
if (client_cb->send_at_reply) {
bta_hf_client_at_result(client_cb, type, cme);
@@ -2139,19 +2148,19 @@
at_len = snprintf(buf, sizeof(buf), "AT+BIA=");
+ const int32_t position = osi_property_get_int32(
+ "bluetooth.headset_client.disable_indicator.position", -1);
+
for (i = 0; i < BTA_HF_CLIENT_AT_INDICATOR_COUNT; i++) {
int sup = client_cb->at_cb.indicator_lookup[i] == -1 ? 0 : 1;
-/* If this value matches the position of SIGNAL in the indicators array,
- * then hardcode disable signal strength indicators.
- * indicator_lookup[i] points to the position in the bta_hf_client_indicators
- * array defined at the top of this file */
-#ifdef BTA_HF_CLIENT_INDICATOR_SIGNAL_POS
- if (client_cb->at_cb.indicator_lookup[i] ==
- BTA_HF_CLIENT_INDICATOR_SIGNAL_POS) {
+ /* If this value matches the position of SIGNAL in the indicators array,
+ * then hardcode disable signal strength indicators.
+ * indicator_lookup[i] points to the position in the
+ * bta_hf_client_indicators array defined at the top of this file */
+ if (client_cb->at_cb.indicator_lookup[i] == position) {
sup = 0;
}
-#endif
at_len += snprintf(buf + at_len, sizeof(buf) - at_len, "%u,", sup);
}
@@ -2185,6 +2194,22 @@
at_len);
}
+void bta_hf_client_send_at_android(tBTA_HF_CLIENT_CB* client_cb,
+ const char* str) {
+ char buf[BTA_HF_CLIENT_AT_MAX_LEN];
+ int at_len;
+
+ APPL_TRACE_DEBUG("%s", __func__);
+
+ at_len = snprintf(buf, sizeof(buf), "AT%s\r", str);
+ if (at_len < 0) {
+ APPL_TRACE_ERROR("%s: AT command Framing error", __func__);
+ return;
+ }
+
+ bta_hf_client_send_at(client_cb, BTA_HF_CLIENT_AT_ANDROID, buf, at_len);
+}
+
void bta_hf_client_at_init(tBTA_HF_CLIENT_CB* client_cb) {
alarm_free(client_cb->at_cb.resp_timer);
alarm_free(client_cb->at_cb.hold_timer);
@@ -2284,6 +2309,9 @@
case BTA_HF_CLIENT_AT_CMD_VENDOR_SPECIFIC_CMD:
bta_hf_client_send_at_vendor_specific_cmd(client_cb, p_val->str);
break;
+ case BTA_HF_CLIENT_AT_CMD_ANDROID:
+ bta_hf_client_send_at_android(client_cb, p_val->str);
+ break;
default:
APPL_TRACE_ERROR("Default case");
snprintf(buf, BTA_HF_CLIENT_AT_MAX_LEN,
diff --git a/system/bta/hf_client/bta_hf_client_int.h b/system/bta/hf_client/bta_hf_client_int.h
index 3c13e6c..cfa63bf 100755
--- a/system/bta/hf_client/bta_hf_client_int.h
+++ b/system/bta/hf_client/bta_hf_client_int.h
@@ -105,6 +105,7 @@
BTA_HF_CLIENT_AT_BIND_READ_ENABLED_IND,
BTA_HF_CLIENT_AT_BIEV,
BTA_HF_CLIENT_AT_VENDOR_SPECIFIC,
+ BTA_HF_CLIENT_AT_ANDROID,
};
/*****************************************************************************
diff --git a/system/bta/hf_client/bta_hf_client_sdp.cc b/system/bta/hf_client/bta_hf_client_sdp.cc
index 4625b6d..cfb88d3 100755
--- a/system/bta/hf_client/bta_hf_client_sdp.cc
+++ b/system/bta/hf_client/bta_hf_client_sdp.cc
@@ -46,6 +46,22 @@
/* Number of elements in service class id list. */
#define BTA_HF_CLIENT_NUM_SVC_ELEMS 2
+#ifdef OS_ANDROID
+#include <hfp.sysprop.h>
+#endif
+
+#define DEFAULT_BTA_HFP_VERSION HFP_VERSION_1_7
+int get_default_hfp_version() {
+#ifdef OS_ANDROID
+ static const int version =
+ android::sysprop::bluetooth::Hfp::version().value_or(
+ DEFAULT_BTA_HFP_VERSION);
+ return version;
+#else
+ return DEFAULT_BTA_HFP_VERSION;
+#endif
+}
+
/*******************************************************************************
*
* Function bta_hf_client_sdp_cback
@@ -124,7 +140,7 @@
/* add profile descriptor list */
profile_uuid = UUID_SERVCLASS_HF_HANDSFREE;
- version = BTA_HFP_VERSION;
+ version = get_default_hfp_version();
result &= SDP_AddProfileDescriptorList(sdp_handle, profile_uuid, version);
diff --git a/system/bta/hfp/bta_hfp_api.cc b/system/bta/hfp/bta_hfp_api.cc
new file mode 100644
index 0000000..63ece44
--- /dev/null
+++ b/system/bta/hfp/bta_hfp_api.cc
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "bta_hfp_api.h"
+
+#ifdef OS_ANDROID
+#include <hfp.sysprop.h>
+#endif
+
+#define DEFAULT_BTA_HFP_VERSION HFP_VERSION_1_7
+
+int get_default_hfp_version() {
+#ifdef OS_ANDROID
+ static const int version =
+ android::sysprop::bluetooth::Hfp::version().value_or(
+ DEFAULT_BTA_HFP_VERSION);
+ return version;
+#else
+ return DEFAULT_BTA_HFP_VERSION;
+#endif
+}
diff --git a/system/bta/hh/bta_hh_act.cc b/system/bta/hh/bta_hh_act.cc
index 38415c3..e768975 100644
--- a/system/bta/hh/bta_hh_act.cc
+++ b/system/bta/hh/bta_hh_act.cc
@@ -159,14 +159,13 @@
******************************************************************************/
void bta_hh_disc_cmpl(void) {
LOG_DEBUG("Disconnect complete");
-
- HID_HostDeregister();
- bta_hh_le_deregister();
tBTA_HH_STATUS status = BTA_HH_OK;
/* Deregister with lower layer */
if (HID_HostDeregister() != HID_SUCCESS) status = BTA_HH_ERR;
+ bta_hh_le_deregister();
+
bta_hh_cleanup_disable(status);
}
diff --git a/system/bta/include/bta_gatt_api.h b/system/bta/include/bta_gatt_api.h
index d334bcc..9fd4db8 100644
--- a/system/bta/include/bta_gatt_api.h
+++ b/system/bta/include/bta_gatt_api.h
@@ -999,4 +999,7 @@
******************************************************************************/
extern void BTA_GATTS_Close(uint16_t conn_id);
+// Adds bonded device for GATT server tracking service changes
+extern void BTA_GATTS_InitBonded(void);
+
#endif /* BTA_GATT_API_H */
diff --git a/system/bta/include/bta_hearing_aid_api.h b/system/bta/include/bta_hearing_aid_api.h
index 90803c5..d1b930b 100644
--- a/system/bta/include/bta_hearing_aid_api.h
+++ b/system/bta/include/bta_hearing_aid_api.h
@@ -228,7 +228,6 @@
base::Closure initCb);
static void CleanUp();
static bool IsHearingAidRunning();
- static HearingAid* Get();
static void DebugDump(int fd);
static void AddFromStorage(const HearingDevice& dev_info,
@@ -236,10 +235,10 @@
static int GetDeviceCount();
- virtual void Connect(const RawAddress& address) = 0;
- virtual void Disconnect(const RawAddress& address) = 0;
- virtual void AddToAcceptlist(const RawAddress& address) = 0;
- virtual void SetVolume(int8_t volume) = 0;
+ static void Connect(const RawAddress& address);
+ static void Disconnect(const RawAddress& address);
+ static void AddToAcceptlist(const RawAddress& address);
+ static void SetVolume(int8_t volume);
};
/* Represents configuration of audio codec, as exchanged between hearing aid and
diff --git a/system/bta/include/bta_hf_client_api.h b/system/bta/include/bta_hf_client_api.h
old mode 100755
new mode 100644
index 32822c2..fe0728e
--- a/system/bta/include/bta_hf_client_api.h
+++ b/system/bta/include/bta_hf_client_api.h
@@ -172,6 +172,7 @@
#define BTA_HF_CLIENT_AT_CMD_NREC 15
#define BTA_HF_CLIENT_AT_CMD_VENDOR_SPECIFIC_CMD 16
#define BTA_HF_CLIENT_AT_CMD_BIEV 17
+#define BTA_HF_CLIENT_AT_CMD_ANDROID 18
typedef uint8_t tBTA_HF_CLIENT_AT_CMD_TYPE;
@@ -321,10 +322,10 @@
* calls to do any AT operations
*
*
- * Returns void
+ * Returns bt_status_t
*
******************************************************************************/
-void BTA_HfClientOpen(const RawAddress& bd_addr, uint16_t* p_handle);
+bt_status_t BTA_HfClientOpen(const RawAddress& bd_addr, uint16_t* p_handle);
/*******************************************************************************
*
@@ -390,4 +391,15 @@
******************************************************************************/
void BTA_HfClientDumpStatistics(int fd);
+/*******************************************************************************
+ *
+ * function get_default_hf_client_features
+ *
+ * description return the hf_client features.
+ * value can be override via system property
+ *
+ * returns int
+ *
+ ******************************************************************************/
+int get_default_hf_client_features();
#endif /* BTA_HF_CLIENT_API_H */
diff --git a/system/bta/include/bta_hfp_api.h b/system/bta/include/bta_hfp_api.h
index a3dd3a9..5c3b59b 100644
--- a/system/bta/include/bta_hfp_api.h
+++ b/system/bta/include/bta_hfp_api.h
@@ -24,6 +24,7 @@
#define HFP_VERSION_1_5 0x0105
#define HFP_VERSION_1_6 0x0106
#define HFP_VERSION_1_7 0x0107
+#define HFP_VERSION_1_8 0x0108
#define HSP_VERSION_1_0 0x0100
#define HSP_VERSION_1_2 0x0102
@@ -31,9 +32,6 @@
#define HFP_VERSION_CONFIG_KEY "HfpVersion"
#define HFP_SDP_FEATURES_CONFIG_KEY "HfpSdpFeatures"
-/* Default HFP Version */
-#ifndef BTA_HFP_VERSION
-#define BTA_HFP_VERSION HFP_VERSION_1_7
-#endif
+int get_default_hfp_version();
#endif /* BTA_HFP_API_H */
\ No newline at end of file
diff --git a/system/bta/le_audio/audio_hal_client/audio_hal_client_test.cc b/system/bta/le_audio/audio_hal_client/audio_hal_client_test.cc
index f55eee0..dfbb026 100644
--- a/system/bta/le_audio/audio_hal_client/audio_hal_client_test.cc
+++ b/system/bta/le_audio/audio_hal_client/audio_hal_client_test.cc
@@ -34,6 +34,7 @@
using ::testing::Assign;
using ::testing::AtLeast;
using ::testing::DoAll;
+using ::testing::DoDefault;
using ::testing::Invoke;
using ::testing::Mock;
using ::testing::Return;
@@ -476,6 +477,13 @@
// Return exactly as much data as requested
promise.set_value();
return len;
+ }))
+ .WillRepeatedly(Invoke([](uint8_t* p_buf, uint32_t len) -> uint32_t {
+ // fake some data from audio framework
+ for (uint32_t i = 0u; i < len; ++i) {
+ p_buf[i] = i;
+ }
+ return len;
}));
std::promise<void> data_promise;
@@ -484,10 +492,12 @@
/* Expect this callback to be called to Client by the HAL glue layer */
std::vector<uint8_t> media_data_to_send;
EXPECT_CALL(mock_hal_sink_event_receiver_, OnAudioDataReady(_))
+ .Times(AtLeast(1))
.WillOnce(Invoke([&](const std::vector<uint8_t>& data) -> void {
media_data_to_send = std::move(data);
data_promise.set_value();
- }));
+ }))
+ .WillRepeatedly(DoDefault());
/* Expect LeAudio registered event listener to get called when HAL calls the
* audio_hal_client's internal resume callback.
diff --git a/system/bta/le_audio/client.cc b/system/bta/le_audio/client.cc
index b5061ef..1d3cb3f 100644
--- a/system/bta/le_audio/client.cc
+++ b/system/bta/le_audio/client.cc
@@ -460,6 +460,8 @@
ToString(group->GetTargetState()).c_str());
group->SetTargetState(AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
+ group->PrintDebugState();
+
/* 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
*/
@@ -483,6 +485,12 @@
void UpdateContextAndLocations(LeAudioDeviceGroup* group,
LeAudioDevice* leAudioDevice) {
+ if (leAudioDevice->GetConnectionState() != DeviceConnectState::CONNECTED) {
+ LOG_DEBUG("%s not yet connected ",
+ leAudioDevice->address_.ToString().c_str());
+ return;
+ }
+
/* Make sure location and direction are updated for the group. */
auto location_update = group->ReloadAudioLocations();
group->ReloadAudioDirections();
@@ -1342,15 +1350,15 @@
leAudioDevice->SetConnectionState(DeviceConnectState::DISCONNECTING);
- if (acl_force_disconnect) {
- leAudioDevice->DisconnectAcl();
- return;
- }
-
BtaGattQueue::Clean(leAudioDevice->conn_id_);
BTA_GATTC_Close(leAudioDevice->conn_id_);
leAudioDevice->conn_id_ = GATT_INVALID_CONN_ID;
leAudioDevice->mtu_ = 0;
+
+ /* Remote in bad state, force ACL Disconnection. */
+ if (acl_force_disconnect) {
+ leAudioDevice->DisconnectAcl();
+ }
}
void DeregisterNotifications(LeAudioDevice* leAudioDevice) {
@@ -1910,19 +1918,43 @@
return iter == charac.descriptors.end() ? 0 : (*iter).handle;
}
- void OnServiceChangeEvent(const RawAddress& address) {
- LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address);
+ void ClearDeviceInformationAndStartSearch(LeAudioDevice* leAudioDevice) {
if (!leAudioDevice) {
- DLOG(ERROR) << __func__
- << ", skipping unknown leAudioDevice, address: " << address;
+ LOG_WARN("leAudioDevice is null");
return;
}
- LOG(INFO) << __func__ << ": address=" << address;
+ LOG_INFO("%s", leAudioDevice->address_.ToString().c_str());
+
+ if (leAudioDevice->known_service_handles_ == false) {
+ LOG_DEBUG("Database already invalidated");
+ return;
+ }
+
leAudioDevice->known_service_handles_ = false;
leAudioDevice->csis_member_ = false;
BtaGattQueue::Clean(leAudioDevice->conn_id_);
DeregisterNotifications(leAudioDevice);
+
+ if (leAudioDevice->GetConnectionState() == DeviceConnectState::CONNECTED) {
+ leAudioDevice->SetConnectionState(
+ DeviceConnectState::CONNECTED_BY_USER_GETTING_READY);
+ }
+
+ btif_storage_remove_leaudio(leAudioDevice->address_);
+
+ BTA_GATTC_ServiceSearchRequest(
+ leAudioDevice->conn_id_,
+ &le_audio::uuid::kPublishedAudioCapabilityServiceUuid);
+ }
+
+ void OnServiceChangeEvent(const RawAddress& address) {
+ LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address);
+ if (!leAudioDevice) {
+ LOG_WARN("Skipping unknown leAudioDevice %s", address.ToString().c_str());
+ return;
+ }
+ ClearDeviceInformationAndStartSearch(leAudioDevice);
}
void OnMtuChanged(uint16_t conn_id, uint16_t mtu) {
@@ -2325,6 +2357,13 @@
return;
}
+ if (status == GATT_DATABASE_OUT_OF_SYNC) {
+ LOG_INFO("Database out of sync for %s, conn_id: 0x%04x",
+ leAudioDevice->address_.ToString().c_str(), conn_id);
+ ClearDeviceInformationAndStartSearch(leAudioDevice);
+ return;
+ }
+
if (status == GATT_SUCCESS) {
LOG(INFO) << __func__
<< ", successfully registered on ccc: " << loghex(hdl);
@@ -3919,14 +3958,18 @@
void* data) {
if (!instance) return;
+ LeAudioDevice* leAudioDevice =
+ instance->leAudioDevices_.FindByConnId(conn_id);
+
if (status == GATT_SUCCESS) {
instance->LeAudioCharValueHandle(conn_id, hdl, len, value);
+ } else if (status == GATT_DATABASE_OUT_OF_SYNC) {
+ instance->ClearDeviceInformationAndStartSearch(leAudioDevice);
+ return;
}
/* We use data to keep notify connected flag. */
if (data && !!PTR_TO_INT(data)) {
- LeAudioDevice* leAudioDevice =
- instance->leAudioDevices_.FindByConnId(conn_id);
leAudioDevice->notify_connected_after_read_ = false;
/* Update PACs and ASEs when all is read.*/
diff --git a/system/bta/le_audio/devices.cc b/system/bta/le_audio/devices.cc
index 16c79b5..13cfa78 100644
--- a/system/bta/le_audio/devices.cc
+++ b/system/bta/le_audio/devices.cc
@@ -826,7 +826,9 @@
codec_spec_conf::kLeAudioLocationNotAllowed;
for (const auto& device : leAudioDevices_) {
- if (device.expired()) continue;
+ if (device.expired() || (device.lock().get()->GetConnectionState() !=
+ DeviceConnectState::CONNECTED))
+ continue;
updated_snk_audio_locations_ |= device.lock().get()->snk_audio_locations_;
updated_src_audio_locations_ |= device.lock().get()->src_audio_locations_;
}
@@ -846,7 +848,9 @@
uint8_t updated_audio_directions = 0x00;
for (const auto& device : leAudioDevices_) {
- if (device.expired()) continue;
+ if (device.expired() || (device.lock().get()->GetConnectionState() !=
+ DeviceConnectState::CONNECTED))
+ continue;
updated_audio_directions |= device.lock().get()->audio_directions_;
}
@@ -863,7 +867,8 @@
}
bool LeAudioDeviceGroup::IsReleasingOrIdle(void) {
- return target_state_ == AseState::BTA_LE_AUDIO_ASE_STATE_IDLE;
+ return (target_state_ == AseState::BTA_LE_AUDIO_ASE_STATE_IDLE) ||
+ (current_state_ == AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
}
bool LeAudioDeviceGroup::IsGroupStreamReady(void) {
@@ -878,16 +883,12 @@
return iter == leAudioDevices_.end();
}
-bool LeAudioDeviceGroup::HaveAllActiveDevicesCisDisc(void) {
- auto iter =
- std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(), [](auto& d) {
- if (d.expired())
- return false;
- else
- return !(((d.lock()).get())->HaveAllAsesCisDisc());
- });
-
- return iter == leAudioDevices_.end();
+bool LeAudioDeviceGroup::HaveAllCisesDisconnected(void) {
+ for (auto const dev : leAudioDevices_) {
+ if (dev.expired()) continue;
+ if (dev.lock().get()->HaveAnyCisConnected()) return false;
+ }
+ return true;
}
uint8_t LeAudioDeviceGroup::GetFirstFreeCisId(void) {
@@ -1380,7 +1381,6 @@
if (audio_location_ulong & codec_spec_conf::kLeAudioLocationLeftSurround)
return codec_spec_conf::kLeAudioLocationLeftSurround;
- LOG_WARN("Can't find device able to render left audio channel");
return 0;
}
@@ -1418,15 +1418,15 @@
if (audio_location_ulong & codec_spec_conf::kLeAudioLocationRightSurround)
return codec_spec_conf::kLeAudioLocationRightSurround;
- LOG_WARN("Can't find device able to render right audio channel");
return 0;
}
uint32_t PickAudioLocation(types::LeAudioConfigurationStrategy strategy,
types::AudioLocations device_locations,
types::AudioLocations* group_locations) {
- LOG_DEBUG("strategy: %d, locations: %lx, group locations: %lx", (int)strategy,
- device_locations.to_ulong(), group_locations->to_ulong());
+ LOG_DEBUG("strategy: %d, locations: 0x%lx, group locations: 0x%lx",
+ (int)strategy, device_locations.to_ulong(),
+ group_locations->to_ulong());
auto is_left_not_yet_assigned =
!(group_locations->to_ulong() & codec_spec_conf::kLeAudioLocationAnyLeft);
@@ -1435,6 +1435,10 @@
uint32_t left_device_loc = GetFirstLeft(device_locations);
uint32_t right_device_loc = GetFirstRight(device_locations);
+ if (left_device_loc == 0 && right_device_loc == 0) {
+ LOG_WARN("Can't find device able to render left and right audio channel");
+ }
+
switch (strategy) {
case types::LeAudioConfigurationStrategy::MONO_ONE_CIS_PER_DEVICE:
case types::LeAudioConfigurationStrategy::STEREO_TWO_CISES_PER_DEVICE:
@@ -1492,6 +1496,12 @@
return false;
}
+ /* The number_of_already_active_group_ase keeps all the active ases
+ * in other devices in the group.
+ * This function counts active ases only for this device, and we count here
+ * new active ases and already active ases which we want to reuse in the
+ * scenario
+ */
uint8_t active_ases = *number_of_already_active_group_ase;
uint8_t max_required_ase_per_dev =
ent.ase_cnt / ent.device_cnt + (ent.ase_cnt % ent.device_cnt);
@@ -1519,40 +1529,48 @@
ase->configured_for_context_type = context_type;
active_ases++;
- if (ase->state == AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED)
- ase->reconfigure = true;
+ /* In case of late connect, we could be here for STREAMING ase.
+ * in such case, it is needed to mark ase as known active ase which
+ * is important to validate scenario and is done already few lines above.
+ * Nothing more to do is needed here.
+ */
+ if (ase->state != AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
+ if (ase->state == AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED)
+ ase->reconfigure = true;
- ase->target_latency = ent.target_latency;
- ase->codec_id = ent.codec.id;
- /* TODO: find better way to not use LC3 explicitly */
- ase->codec_config = std::get<LeAudioLc3Config>(ent.codec.config);
+ ase->target_latency = ent.target_latency;
+ ase->codec_id = ent.codec.id;
+ /* TODO: find better way to not use LC3 explicitly */
+ ase->codec_config = std::get<LeAudioLc3Config>(ent.codec.config);
- /*Let's choose audio channel allocation if not set */
- ase->codec_config.audio_channel_allocation =
- PickAudioLocation(strategy, audio_locations, group_audio_locations);
+ /*Let's choose audio channel allocation if not set */
+ ase->codec_config.audio_channel_allocation =
+ PickAudioLocation(strategy, audio_locations, group_audio_locations);
- /* Get default value if no requirement for specific frame blocks per sdu */
- if (!ase->codec_config.codec_frames_blocks_per_sdu) {
- ase->codec_config.codec_frames_blocks_per_sdu =
- GetMaxCodecFramesPerSduFromPac(pac);
- }
- ase->max_sdu_size = codec_spec_caps::GetAudioChannelCounts(
- *ase->codec_config.audio_channel_allocation) *
- *ase->codec_config.octets_per_codec_frame *
- *ase->codec_config.codec_frames_blocks_per_sdu;
+ /* Get default value if no requirement for specific frame blocks per sdu
+ */
+ if (!ase->codec_config.codec_frames_blocks_per_sdu) {
+ ase->codec_config.codec_frames_blocks_per_sdu =
+ GetMaxCodecFramesPerSduFromPac(pac);
+ }
+ ase->max_sdu_size = codec_spec_caps::GetAudioChannelCounts(
+ *ase->codec_config.audio_channel_allocation) *
+ *ase->codec_config.octets_per_codec_frame *
+ *ase->codec_config.codec_frames_blocks_per_sdu;
- ase->retrans_nb = ent.qos.retransmission_number;
- ase->max_transport_latency = ent.qos.max_transport_latency;
+ ase->retrans_nb = ent.qos.retransmission_number;
+ ase->max_transport_latency = ent.qos.max_transport_latency;
- /* Filter multidirectional audio context for each ase direction */
- auto directional_audio_context =
- metadata_context_type & GetAvailableContexts(ase->direction);
- if (directional_audio_context.any()) {
- ase->metadata = GetMetadata(directional_audio_context, ccid_list);
- } else {
- ase->metadata =
- GetMetadata(AudioContexts(LeAudioContextType::UNSPECIFIED),
- std::vector<uint8_t>());
+ /* Filter multidirectional audio context for each ase direction */
+ auto directional_audio_context =
+ metadata_context_type & GetAvailableContexts(ase->direction);
+ if (directional_audio_context.any()) {
+ ase->metadata = GetMetadata(directional_audio_context, ccid_list);
+ } else {
+ ase->metadata =
+ GetMetadata(AudioContexts(LeAudioContextType::UNSPECIFIED),
+ std::vector<uint8_t>());
+ }
}
LOG_DEBUG(
@@ -1956,6 +1974,61 @@
}
LeAudioDeviceGroup::~LeAudioDeviceGroup(void) { this->Cleanup(); }
+
+void LeAudioDeviceGroup::PrintDebugState(void) {
+ auto* active_conf = GetActiveConfiguration();
+ std::stringstream debug_str;
+
+ debug_str << "\n Groupd id: " << group_id_
+ << ", state: " << bluetooth::common::ToString(GetState())
+ << ", target state: "
+ << bluetooth::common::ToString(GetTargetState())
+ << ", cig state: " << bluetooth::common::ToString(cig_state_)
+ << ", \n group available contexts: "
+ << bluetooth::common::ToString(GetAvailableContexts())
+ << ", \n configuration context type: "
+ << bluetooth::common::ToString(GetConfigurationContextType())
+ << ", \n active configuration name: "
+ << (active_conf ? active_conf->name : " not set");
+
+ if (cises_.size() > 0) {
+ LOG_INFO("\n Allocated CISes: %d", static_cast<int>(cises_.size()));
+ for (auto cis : cises_) {
+ LOG_INFO("\n cis id: %d, type: %d, conn_handle %d, addr: %s", cis.id,
+ cis.type, cis.conn_handle, cis.addr.ToString().c_str());
+ }
+ }
+
+ if (GetFirstActiveDevice() != nullptr) {
+ uint32_t sink_delay = 0;
+ uint32_t source_delay = 0;
+ GetPresentationDelay(&sink_delay, le_audio::types::kLeAudioDirectionSink);
+ GetPresentationDelay(&source_delay,
+ le_audio::types::kLeAudioDirectionSource);
+ auto phy_mtos = GetPhyBitmask(le_audio::types::kLeAudioDirectionSink);
+ auto phy_stom = GetPhyBitmask(le_audio::types::kLeAudioDirectionSource);
+ auto max_transport_latency_mtos = GetMaxTransportLatencyMtos();
+ auto max_transport_latency_stom = GetMaxTransportLatencyStom();
+ auto sdu_mts = GetSduInterval(le_audio::types::kLeAudioDirectionSink);
+ auto sdu_stom = GetSduInterval(le_audio::types::kLeAudioDirectionSource);
+
+ debug_str << "\n resentation_delay for sink (speaker): " << +sink_delay
+ << " us, presentation_delay for source (microphone): "
+ << +source_delay << "us, \n MtoS transport latency: "
+ << +max_transport_latency_mtos
+ << ", StoM transport latency: " << +max_transport_latency_stom
+ << ", \n MtoS Phy: " << loghex(phy_mtos)
+ << ", MtoS sdu: " << loghex(phy_stom)
+ << " \n MtoS sdu: " << +sdu_mts << ", StoM sdu: " << +sdu_stom;
+ }
+
+ LOG_INFO("%s", debug_str.str().c_str());
+
+ for (const auto& device_iter : leAudioDevices_) {
+ device_iter.lock()->PrintDebugState();
+ }
+}
+
void LeAudioDeviceGroup::Dump(int fd, int active_group_id) {
bool is_active = (group_id_ == active_group_id);
std::stringstream stream;
@@ -2324,13 +2397,15 @@
return iter == ases_.end();
}
-bool LeAudioDevice::HaveAllAsesCisDisc(void) {
- auto iter = std::find_if(ases_.begin(), ases_.end(), [](const auto& ase) {
- return ase.active &&
- (ase.data_path_state != AudioStreamDataPathState::CIS_ASSIGNED);
- });
-
- return iter == ases_.end();
+bool LeAudioDevice::HaveAnyCisConnected(void) {
+ /* Pending and Disconnecting is considered as connected in this function */
+ for (auto const ase : ases_) {
+ if (ase.data_path_state != AudioStreamDataPathState::CIS_ASSIGNED &&
+ ase.data_path_state != AudioStreamDataPathState::IDLE) {
+ return true;
+ }
+ }
+ return false;
}
bool LeAudioDevice::HasCisId(uint8_t id) {
@@ -2450,31 +2525,75 @@
supp_contexts_.source = src_contexts;
}
+void LeAudioDevice::PrintDebugState(void) {
+ std::stringstream debug_str;
+
+ debug_str << " address: " << address_ << ", "
+ << bluetooth::common::ToString(connection_state_)
+ << ", conn_id: " << +conn_id_ << ", mtu: " << +mtu_
+ << ", num_of_ase: " << static_cast<int>(ases_.size());
+
+ if (ases_.size() > 0) {
+ debug_str << "\n == ASEs == ";
+ for (auto& ase : ases_) {
+ debug_str << "\n id: " << +ase.id << ", active: " << ase.active
+ << ", dir: "
+ << (ase.direction == types::kLeAudioDirectionSink ? "sink"
+ : "source")
+ << ", cis_id: " << +ase.cis_id
+ << ", cis_handle: " << +ase.cis_conn_hdl << ", state: "
+ << bluetooth::common::ToString(ase.data_path_state)
+ << "\n ase max_latency: " << +ase.max_transport_latency
+ << ", rtn: " << +ase.retrans_nb
+ << ", max_sdu: " << +ase.max_sdu_size
+ << ", target latency: " << +ase.target_latency;
+ }
+ }
+
+ LOG_INFO("%s", debug_str.str().c_str());
+}
+
void LeAudioDevice::Dump(int fd) {
uint16_t acl_handle = BTM_GetHCIConnHandle(address_, BT_TRANSPORT_LE);
+ std::string location = "unknown location";
+
+ if (snk_audio_locations_.to_ulong() &
+ codec_spec_conf::kLeAudioLocationAnyLeft) {
+ std::string location_left = "left";
+ location.swap(location_left);
+ } else if (snk_audio_locations_.to_ulong() &
+ codec_spec_conf::kLeAudioLocationAnyRight) {
+ std::string location_right = "right";
+ location.swap(location_right);
+ }
std::stringstream stream;
- stream << std::boolalpha;
stream << "\n\taddress: " << address_ << ": " << connection_state_ << ": "
<< (conn_id_ == GATT_INVALID_CONN_ID ? "" : std::to_string(conn_id_))
- << ", acl_handle: " << std::to_string(acl_handle) << ",\t"
- << (encrypted_ ? "Encrypted" : "Unecrypted")
+ << ", acl_handle: " << std::to_string(acl_handle) << ", " << location
+ << ",\t" << (encrypted_ ? "Encrypted" : "Unecrypted")
<< ",mtu: " << std::to_string(mtu_)
<< "\n\tnumber of ases_: " << static_cast<int>(ases_.size());
if (ases_.size() > 0) {
- stream << "\n\t == ASEs == ";
+ stream << "\n\t== ASEs == \n\t";
+ stream
+ << "id active dir cis_id cis_handle sdu latency rtn state";
for (auto& ase : ases_) {
- stream << "\n\t id: " << static_cast<int>(ase.id)
- << ",\tactive: " << ase.active << ", dir: "
+ stream << std::setfill('\xA0') << "\n\t" << std::left << std::setw(4)
+ << static_cast<int>(ase.id) << std::left << std::setw(7)
+ << (ase.active ? "true" : "false") << std::left << std::setw(8)
<< (ase.direction == types::kLeAudioDirectionSink ? "sink"
: "source")
- << ",\tcis_id: " << static_cast<int>(ase.cis_id)
- << ",\tcis_handle: " << ase.cis_conn_hdl << ",\tstate: "
+ << std::left << std::setw(8) << static_cast<int>(ase.cis_id)
+ << std::left << std::setw(12) << ase.cis_conn_hdl << std::left
+ << std::setw(5) << ase.max_sdu_size << std::left << std::setw(8)
+ << ase.max_transport_latency << std::left << std::setw(5)
+ << static_cast<int>(ase.retrans_nb) << std::left << std::setw(12)
<< bluetooth::common::ToString(ase.data_path_state);
}
}
- stream << "\n\t ====";
+ stream << "\n\t====";
dprintf(fd, "%s", stream.str().c_str());
}
diff --git a/system/bta/le_audio/devices.h b/system/bta/le_audio/devices.h
index 11f31de..d9f4900 100644
--- a/system/bta/le_audio/devices.h
+++ b/system/bta/le_audio/devices.h
@@ -155,7 +155,7 @@
bool IsReadyToCreateStream(void);
bool IsReadyToSuspendStream(void);
bool HaveAllActiveAsesCisEst(void);
- bool HaveAllAsesCisDisc(void);
+ bool HaveAnyCisConnected(void);
bool HasCisId(uint8_t id);
uint8_t GetMatchingBidirectionCisId(const struct types::ase* base_ase);
const struct types::acs_ac_record* GetCodecConfigurationSupportedPac(
@@ -179,7 +179,10 @@
types::AudioContexts src_cont_val);
void DeactivateAllAses(void);
bool ActivateConfiguredAses(types::LeAudioContextType context_type);
+
+ void PrintDebugState(void);
void Dump(int fd);
+
void DisconnectAcl(void);
std::vector<uint8_t> GetMetadata(types::AudioContexts context_type,
const std::vector<uint8_t>& ccid_list);
@@ -283,7 +286,7 @@
bool IsDeviceInTheGroup(LeAudioDevice* leAudioDevice);
bool HaveAllActiveDevicesAsesTheSameState(types::AseState state);
bool IsGroupStreamReady(void);
- bool HaveAllActiveDevicesCisDisc(void);
+ bool HaveAllCisesDisconnected(void);
uint8_t GetFirstFreeCisId(void);
uint8_t GetFirstFreeCisId(types::CisType cis_type);
void CigGenerateCisIds(types::LeAudioContextType context_type);
@@ -370,6 +373,8 @@
bool IsInTransition(void);
bool IsReleasingOrIdle(void);
+
+ void PrintDebugState(void);
void Dump(int fd, int active_group_id);
private:
diff --git a/system/bta/le_audio/le_audio_client_test.cc b/system/bta/le_audio/le_audio_client_test.cc
index 54fe8f7..69978b3 100644
--- a/system/bta/le_audio/le_audio_client_test.cc
+++ b/system/bta/le_audio/le_audio_client_test.cc
@@ -1463,6 +1463,7 @@
base::Unretained(LeAudioClient::Get()), address));
SyncOnMainLoop();
+ Mock::VerifyAndClearExpectations(&mock_gatt_interface_);
}
void DisconnectLeAudio(const RawAddress& address, uint16_t conn_id) {
@@ -4085,4 +4086,61 @@
LeAudioClient::Get()->SetInCall(false);
}
+TEST_F(UnicastTest, HandleDatabaseOutOfSync) {
+ const RawAddress test_address0 = GetTestAddress(0);
+ int group_id = bluetooth::groups::kGroupUnknown;
+
+ SetSampleDatabaseEarbudsValid(
+ 1, test_address0, codec_spec_conf::kLeAudioLocationStereo,
+ codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt,
+ default_channel_cnt, 0x0004, false /*add_csis*/, true /*add_cas*/,
+ true /*add_pacs*/, default_ase_cnt /*add_ascs_cnt*/, 1 /*set_size*/,
+ 0 /*rank*/);
+ EXPECT_CALL(mock_audio_hal_client_callbacks_,
+ OnConnectionState(ConnectionState::CONNECTED, test_address0))
+ .Times(1);
+ EXPECT_CALL(mock_audio_hal_client_callbacks_,
+ OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED))
+ .WillOnce(DoAll(SaveArg<1>(&group_id)));
+
+ ConnectLeAudio(test_address0);
+ ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown);
+
+ SyncOnMainLoop();
+ Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_);
+
+ EXPECT_CALL(mock_audio_hal_client_callbacks_,
+ OnConnectionState(ConnectionState::DISCONNECTED, test_address0))
+ .Times(1);
+ InjectDisconnectedEvent(1, GATT_CONN_TERMINATE_PEER_USER);
+ SyncOnMainLoop();
+ Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_);
+
+ // default action for WriteDescriptor function call
+ ON_CALL(mock_gatt_queue_, WriteDescriptor(_, _, _, _, _, _))
+ .WillByDefault(Invoke([](uint16_t conn_id, uint16_t handle,
+ std::vector<uint8_t> value,
+ tGATT_WRITE_TYPE write_type, GATT_WRITE_OP_CB cb,
+ void* cb_data) -> void {
+ if (cb)
+ do_in_main_thread(
+ FROM_HERE,
+ base::BindOnce(
+ [](GATT_WRITE_OP_CB cb, uint16_t conn_id, uint16_t handle,
+ uint16_t len, uint8_t* value, void* cb_data) {
+ cb(conn_id, GATT_DATABASE_OUT_OF_SYNC, handle, len, value,
+ cb_data);
+ },
+ cb, conn_id, handle, value.size(), value.data(), cb_data));
+ }));
+
+ ON_CALL(mock_gatt_interface_, ServiceSearchRequest(_, _))
+ .WillByDefault(Return());
+ EXPECT_CALL(mock_gatt_interface_, ServiceSearchRequest(_, _));
+
+ InjectConnectedEvent(test_address0, 1);
+ SyncOnMainLoop();
+ Mock::VerifyAndClearExpectations(&mock_gatt_interface_);
+}
+
} // namespace le_audio
diff --git a/system/bta/le_audio/le_audio_types.h b/system/bta/le_audio/le_audio_types.h
index b5935a5..94a722e 100644
--- a/system/bta/le_audio/le_audio_types.h
+++ b/system/bta/le_audio/le_audio_types.h
@@ -627,6 +627,13 @@
data_path_state(AudioStreamDataPathState::IDLE),
configured_for_context_type(LeAudioContextType::UNINITIALIZED),
preferred_phy(0),
+ max_sdu_size(0),
+ retrans_nb(0),
+ max_transport_latency(0),
+ pres_delay_min(0),
+ pres_delay_max(0),
+ preferred_pres_delay_min(0),
+ preferred_pres_delay_max(0),
state(AseState::BTA_LE_AUDIO_ASE_STATE_IDLE) {}
struct hdl_pair hdls;
diff --git a/system/bta/le_audio/state_machine.cc b/system/bta/le_audio/state_machine.cc
index 09bba6f..9b91b43 100644
--- a/system/bta/le_audio/state_machine.cc
+++ b/system/bta/le_audio/state_machine.cc
@@ -227,8 +227,8 @@
}
while (leAudioDevice) {
- PrepareAndSendUpdateMetadata(group, leAudioDevice,
- metadata_context_type, ccid_list);
+ PrepareAndSendUpdateMetadata(leAudioDevice, metadata_context_type,
+ ccid_list);
leAudioDevice = group->GetNextActiveDevice(leAudioDevice);
}
break;
@@ -638,7 +638,7 @@
LOG_DEBUG(
" device: %s, group connected: %d, all active ase disconnected:: %d",
leAudioDevice->address_.ToString().c_str(),
- group->IsAnyDeviceConnected(), group->HaveAllActiveDevicesCisDisc());
+ group->IsAnyDeviceConnected(), group->HaveAllCisesDisconnected());
/* Update the current group audio context availability which could change
* due to disconnected group member.
@@ -649,8 +649,7 @@
* If there is active CIS, do nothing here. Just update the available
* contexts table.
*/
- if (group->IsAnyDeviceConnected() &&
- !group->HaveAllActiveDevicesCisDisc()) {
+ if (group->IsAnyDeviceConnected() && !group->HaveAllCisesDisconnected()) {
if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
/* We keep streaming but want others to let know user that it might be
* need to update offloader with new CIS configuration
@@ -666,6 +665,11 @@
*/
group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
group->SetTargetState(AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
+
+ /* Clear group pending status */
+ group->ClearPendingAvailableContextsChange();
+ group->ClearPendingConfiguration();
+
if (alarm_is_scheduled(watchdog_)) alarm_cancel(watchdog_);
ReleaseCisIds(group);
state_machine_callbacks_->StatusReportCb(group->group_id_,
@@ -677,8 +681,6 @@
LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice,
const bluetooth::hci::iso_manager::cis_establish_cmpl_evt* event)
override {
- std::vector<uint8_t> value;
-
auto ases_pair = leAudioDevice->GetAsesByCisConnHdl(event->cis_conn_hdl);
if (event->status) {
@@ -693,7 +695,7 @@
* or pending. If CIS is established, this will be handled in disconnected
* complete event
*/
- if (group->HaveAllActiveDevicesCisDisc()) {
+ if (group->HaveAllCisesDisconnected()) {
RemoveCigForGroup(group);
}
@@ -730,11 +732,17 @@
}
if (!leAudioDevice->HaveAllActiveAsesCisEst()) {
- /* More cis established event has to come */
+ /* More cis established events has to come */
return;
}
- std::vector<uint8_t> ids;
+ if (!leAudioDevice->IsReadyToCreateStream()) {
+ /* Device still remains in ready to create stream state. It means that
+ * more enabling status notifications has to come. This may only happen
+ * for reconnection scenario for bi-directional CIS.
+ */
+ return;
+ }
/* All CISes created. Send start ready for source ASE before we can go
* to streaming state.
@@ -745,21 +753,8 @@
"id: %d, cis handle 0x%04x",
leAudioDevice->address_.ToString().c_str(), event->cig_id,
event->cis_conn_hdl);
- do {
- if (ase->direction == le_audio::types::kLeAudioDirectionSource)
- ids.push_back(ase->id);
- } while ((ase = leAudioDevice->GetNextActiveAse(ase)));
- if (ids.size() > 0) {
- le_audio::client_parser::ascs::PrepareAseCtpAudioReceiverStartReady(
- ids, value);
-
- BtaGattQueue::WriteCharacteristic(leAudioDevice->conn_id_,
- leAudioDevice->ctp_hdls_.val_hdl, value,
- GATT_WRITE_NO_RSP, NULL, NULL);
-
- return;
- }
+ PrepareAndSendReceiverStartReady(leAudioDevice, ase);
/* Cis establishment may came after setting group state to streaming, e.g.
* for autonomous scenario when ase is sink */
@@ -837,7 +832,7 @@
* If there is other device connected and streaming, just leave it as it
* is, otherwise stop the stream.
*/
- if (!group->HaveAllActiveDevicesCisDisc()) {
+ if (!group->HaveAllCisesDisconnected()) {
/* There is ASE streaming for some device. Continue streaming. */
LOG_WARN(
"Group member disconnected during streaming. Cis handle 0x%04x",
@@ -846,6 +841,7 @@
}
LOG_INFO("Lost all members from the group %d", group->group_id_);
+ group->cises_.clear();
RemoveCigForGroup(group);
group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
@@ -863,7 +859,7 @@
*/
if ((group->GetState() ==
AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED) &&
- group->HaveAllActiveDevicesCisDisc()) {
+ group->HaveAllCisesDisconnected()) {
/* No more transition for group */
alarm_cancel(watchdog_);
@@ -873,15 +869,61 @@
}
break;
case AseState::BTA_LE_AUDIO_ASE_STATE_IDLE:
- case AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED:
+ case AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED: {
/* Those two are used when closing the stream and CIS disconnection is
* expected */
- if (group->HaveAllActiveDevicesCisDisc()) {
- RemoveCigForGroup(group);
+ if (!group->HaveAllCisesDisconnected()) {
+ LOG_DEBUG(
+ "Still waiting for all CISes being disconnected for group:%d",
+ group->group_id_);
return;
}
- break;
+ auto current_group_state = group->GetState();
+ LOG_INFO("group %d current state: %s, target state: %s",
+ group->group_id_,
+ bluetooth::common::ToString(current_group_state).c_str(),
+ bluetooth::common::ToString(target_state).c_str());
+ /* It might happen that controller notified about CIS disconnection
+ * later, after ASE state already changed.
+ * In such an event, there is need to notify upper layer about state
+ * from here.
+ */
+ if (alarm_is_scheduled(watchdog_)) {
+ alarm_cancel(watchdog_);
+ }
+
+ if (current_group_state == AseState::BTA_LE_AUDIO_ASE_STATE_IDLE) {
+ LOG_INFO(
+ "Cises disconnected for group %d, we are good in Idle state.",
+ group->group_id_);
+ ReleaseCisIds(group);
+ state_machine_callbacks_->StatusReportCb(group->group_id_,
+ GroupStreamStatus::IDLE);
+ } else if (current_group_state ==
+ AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED) {
+ auto reconfig = group->IsPendingConfiguration();
+ LOG_INFO(
+ "Cises disconnected for group: %d, we are good in Configured "
+ "state, reconfig=%d.",
+ group->group_id_, reconfig);
+
+ if (reconfig) {
+ group->ClearPendingConfiguration();
+ state_machine_callbacks_->StatusReportCb(
+ group->group_id_, GroupStreamStatus::CONFIGURED_BY_USER);
+ } else {
+ /* This is Autonomous change if both, target and current state
+ * is CODEC_CONFIGURED
+ */
+ if (target_state == current_group_state) {
+ state_machine_callbacks_->StatusReportCb(
+ group->group_id_, GroupStreamStatus::CONFIGURED_AUTONOMOUS);
+ }
+ }
+ }
+ RemoveCigForGroup(group);
+ } break;
default:
break;
}
@@ -1275,6 +1317,15 @@
}
}
+ if ((sdu_interval_mtos == 0 && sdu_interval_stom == 0) ||
+ (max_trans_lat_mtos == le_audio::types::kMaxTransportLatencyMin &&
+ max_trans_lat_stom == le_audio::types::kMaxTransportLatencyMin) ||
+ (max_sdu_size_mtos == 0 && max_sdu_size_stom == 0)) {
+ LOG_ERROR(" Trying to create invalid group");
+ group->PrintDebugState();
+ return false;
+ }
+
bluetooth::hci::iso_manager::cig_create_params param = {
.sdu_itv_mtos = sdu_interval_mtos,
.sdu_itv_stom = sdu_interval_stom,
@@ -1459,9 +1510,21 @@
PrepareAndSendRelease(leAudioDeviceNext);
} else {
/* Last node is in releasing state*/
- if (alarm_is_scheduled(watchdog_)) alarm_cancel(watchdog_);
-
group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
+
+ group->PrintDebugState();
+ /* If all CISes are disconnected, notify upper layer about IDLE state,
+ * otherwise wait for */
+ if (!group->HaveAllCisesDisconnected()) {
+ LOG_WARN(
+ "Not all CISes removed before going to IDLE for group %d, "
+ "waiting...",
+ group->group_id_);
+ group->PrintDebugState();
+ return;
+ }
+
+ if (alarm_is_scheduled(watchdog_)) alarm_cancel(watchdog_);
ReleaseCisIds(group);
state_machine_callbacks_->StatusReportCb(group->group_id_,
GroupStreamStatus::IDLE);
@@ -1642,6 +1705,19 @@
AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED &&
group->IsPendingConfiguration()) {
LOG_INFO(" Configured state completed ");
+
+ /* If all CISes are disconnected, notify upper layer about IDLE
+ * state, otherwise wait for */
+ if (!group->HaveAllCisesDisconnected()) {
+ LOG_WARN(
+ "Not all CISes removed before going to CONFIGURED for group "
+ "%d, "
+ "waiting...",
+ group->group_id_);
+ group->PrintDebugState();
+ return;
+ }
+
group->ClearPendingConfiguration();
state_machine_callbacks_->StatusReportCb(
group->group_id_, GroupStreamStatus::CONFIGURED_BY_USER);
@@ -1780,7 +1856,6 @@
PrepareAndSendRelease(leAudioDeviceNext);
} else {
/* Last node is in releasing state*/
- if (alarm_is_scheduled(watchdog_)) alarm_cancel(watchdog_);
group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED);
/* Remote device has cache and keep staying in configured state after
@@ -1788,6 +1863,18 @@
* remote device.
*/
group->SetTargetState(group->GetState());
+
+ if (!group->HaveAllCisesDisconnected()) {
+ LOG_WARN(
+ "Not all CISes removed before going to IDLE for group %d, "
+ "waiting...",
+ group->group_id_);
+ group->PrintDebugState();
+ return;
+ }
+
+ if (alarm_is_scheduled(watchdog_)) alarm_cancel(watchdog_);
+
state_machine_callbacks_->StatusReportCb(
group->group_id_, GroupStreamStatus::CONFIGURED_AUTONOMOUS);
}
@@ -1876,7 +1963,7 @@
group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED);
- if (!group->HaveAllActiveDevicesCisDisc()) return;
+ if (!group->HaveAllCisesDisconnected()) return;
if (group->GetTargetState() ==
AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED) {
@@ -1972,6 +2059,9 @@
LeAudioDevice* leAudioDevice) {
std::vector<struct le_audio::client_parser::ascs::ctp_qos_conf> confs;
+ bool validate_transport_latency = false;
+ bool validate_max_sdu_size = false;
+
for (struct ase* ase = leAudioDevice->GetFirstActiveAse(); ase != nullptr;
ase = leAudioDevice->GetNextActiveAse(ase)) {
LOG_DEBUG("device: %s, ase_id: %d, cis_id: %d, ase state: %s",
@@ -1988,14 +2078,16 @@
conf.max_sdu = ase->max_sdu_size;
conf.retrans_nb = ase->retrans_nb;
if (!group->GetPresentationDelay(&conf.pres_delay, ase->direction)) {
- LOG(ERROR) << __func__ << ", inconsistent presentation delay for group";
+ LOG_ERROR("inconsistent presentation delay for group");
+ group->PrintDebugState();
StopStream(group);
return;
}
conf.sdu_interval = group->GetSduInterval(ase->direction);
if (!conf.sdu_interval) {
- LOG(ERROR) << __func__ << ", unsupported SDU interval for group";
+ LOG_ERROR("unsupported SDU interval for group");
+ group->PrintDebugState();
StopStream(group);
return;
}
@@ -2005,64 +2097,105 @@
} else {
conf.max_transport_latency = group->GetMaxTransportLatencyStom();
}
+
+ if (conf.max_transport_latency >
+ le_audio::types::kMaxTransportLatencyMin) {
+ validate_transport_latency = true;
+ }
+
+ if (conf.max_sdu > 0) {
+ validate_max_sdu_size = true;
+ }
confs.push_back(conf);
}
- LOG_ASSERT(confs.size() > 0)
- << __func__ << " shouldn't be called without an active ASE";
+ if (confs.size() == 0 || !validate_transport_latency ||
+ !validate_max_sdu_size) {
+ LOG_ERROR("Invalid configuration or latency or sdu size");
+ group->PrintDebugState();
+ StopStream(group);
+ return;
+ }
std::vector<uint8_t> value;
le_audio::client_parser::ascs::PrepareAseCtpConfigQos(confs, value);
-
BtaGattQueue::WriteCharacteristic(leAudioDevice->conn_id_,
leAudioDevice->ctp_hdls_.val_hdl, value,
GATT_WRITE_NO_RSP, NULL, NULL);
}
- void PrepareAndSendUpdateMetadata(LeAudioDeviceGroup* group,
- LeAudioDevice* leAudioDevice,
+ void PrepareAndSendUpdateMetadata(LeAudioDevice* leAudioDevice,
le_audio::types::AudioContexts context_type,
const std::vector<uint8_t>& ccid_list) {
std::vector<struct le_audio::client_parser::ascs::ctp_update_metadata>
confs;
- for (; leAudioDevice;
- leAudioDevice = group->GetNextActiveDevice(leAudioDevice)) {
- if (!leAudioDevice->IsMetadataChanged(context_type, ccid_list)) continue;
+ if (!leAudioDevice->IsMetadataChanged(context_type, ccid_list)) return;
- /* Request server to update ASEs with new metadata */
- for (struct ase* ase = leAudioDevice->GetFirstActiveAse(); ase != nullptr;
- ase = leAudioDevice->GetNextActiveAse(ase)) {
- LOG_DEBUG("device: %s, ase_id: %d, cis_id: %d, ase state: %s",
- leAudioDevice->address_.ToString().c_str(), ase->id,
- ase->cis_id, ToString(ase->state).c_str());
+ /* Request server to update ASEs with new metadata */
+ for (struct ase* ase = leAudioDevice->GetFirstActiveAse(); ase != nullptr;
+ ase = leAudioDevice->GetNextActiveAse(ase)) {
+ LOG_DEBUG("device: %s, ase_id: %d, cis_id: %d, ase state: %s",
+ leAudioDevice->address_.ToString().c_str(), ase->id,
+ ase->cis_id, ToString(ase->state).c_str());
- /* Filter multidirectional audio context for each ase direction */
- auto directional_audio_context =
- context_type & leAudioDevice->GetAvailableContexts(ase->direction);
- if (directional_audio_context.any()) {
- ase->metadata =
- leAudioDevice->GetMetadata(directional_audio_context, ccid_list);
- } else {
- ase->metadata = leAudioDevice->GetMetadata(
- AudioContexts(LeAudioContextType::UNSPECIFIED),
- std::vector<uint8_t>());
- }
-
- struct le_audio::client_parser::ascs::ctp_update_metadata conf;
-
- conf.ase_id = ase->id;
- conf.metadata = ase->metadata;
-
- confs.push_back(conf);
+ if (ase->state != AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING &&
+ ase->state != AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
+ /* This might happen when update metadata happens on late connect */
+ LOG_DEBUG(
+ "Metadata for ase_id %d cannot be updated due to invalid ase state "
+ "- see log above",
+ ase->id);
+ continue;
}
+ /* Filter multidirectional audio context for each ase direction */
+ auto directional_audio_context =
+ context_type & leAudioDevice->GetAvailableContexts(ase->direction);
+ if (directional_audio_context.any()) {
+ ase->metadata =
+ leAudioDevice->GetMetadata(directional_audio_context, ccid_list);
+ } else {
+ ase->metadata = leAudioDevice->GetMetadata(
+ AudioContexts(LeAudioContextType::UNSPECIFIED),
+ std::vector<uint8_t>());
+ }
+
+ struct le_audio::client_parser::ascs::ctp_update_metadata conf;
+
+ conf.ase_id = ase->id;
+ conf.metadata = ase->metadata;
+
+ confs.push_back(conf);
+ }
+
+ if (confs.size() != 0) {
std::vector<uint8_t> value;
le_audio::client_parser::ascs::PrepareAseCtpUpdateMetadata(confs, value);
BtaGattQueue::WriteCharacteristic(leAudioDevice->conn_id_,
leAudioDevice->ctp_hdls_.val_hdl, value,
GATT_WRITE_NO_RSP, NULL, NULL);
+ }
+ }
+
+ void PrepareAndSendReceiverStartReady(LeAudioDevice* leAudioDevice,
+ struct ase* ase) {
+ std::vector<uint8_t> ids;
+ std::vector<uint8_t> value;
+
+ do {
+ if (ase->direction == le_audio::types::kLeAudioDirectionSource)
+ ids.push_back(ase->id);
+ } while ((ase = leAudioDevice->GetNextActiveAse(ase)));
+
+ if (ids.size() > 0) {
+ le_audio::client_parser::ascs::PrepareAseCtpAudioReceiverStartReady(
+ ids, value);
+
+ BtaGattQueue::WriteCharacteristic(leAudioDevice->conn_id_,
+ leAudioDevice->ctp_hdls_.val_hdl, value,
+ GATT_WRITE_NO_RSP, NULL, NULL);
return;
}
@@ -2076,17 +2209,40 @@
return;
}
- if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
- /* We are here because of the reconnection of the single device. */
- ase->state = AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING;
- CisCreateForDevice(leAudioDevice);
- return;
- }
-
switch (ase->state) {
case AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED:
ase->state = AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING;
+ if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
+ if (ase->data_path_state < AudioStreamDataPathState::CIS_PENDING) {
+ /* We are here because of the reconnection of the single device. */
+ CisCreateForDevice(leAudioDevice);
+ }
+
+ if (!leAudioDevice->HaveAllActiveAsesCisEst()) {
+ /* More cis established events has to come */
+ return;
+ }
+
+ if (!leAudioDevice->IsReadyToCreateStream()) {
+ /* Device still remains in ready to create stream state. It means
+ * that more enabling status notifications has to come.
+ */
+ return;
+ }
+
+ /* All CISes created. Send start ready for source ASE before we can go
+ * to streaming state.
+ */
+ struct ase* ase = leAudioDevice->GetFirstActiveAse();
+ ASSERT_LOG(ase != nullptr,
+ "shouldn't be called without an active ASE, device %s",
+ leAudioDevice->address_.ToString().c_str());
+ PrepareAndSendReceiverStartReady(leAudioDevice, ase);
+
+ return;
+ }
+
if (leAudioDevice->IsReadyToCreateStream())
ProcessGroupEnable(group, leAudioDevice);
diff --git a/system/bta/le_audio/state_machine_test.cc b/system/bta/le_audio/state_machine_test.cc
index 2d0fc44..dc5cfe9 100644
--- a/system/bta/le_audio/state_machine_test.cc
+++ b/system/bta/le_audio/state_machine_test.cc
@@ -449,6 +449,11 @@
}
void TearDown() override {
+ /* Clear the alarm on tear down in case test case ends when the
+ * alarm is scheduled
+ */
+ alarm_cancel(nullptr);
+
iso_manager_->Stop();
mock_iso_manager_ = nullptr;
codec_manager_->Stop();
@@ -865,7 +870,7 @@
codec_configured_state_params.framing =
ascs::kAseParamFramingUnframedSupported;
codec_configured_state_params.preferred_retrans_nb = 0x04;
- codec_configured_state_params.max_transport_latency = 0x0005;
+ codec_configured_state_params.max_transport_latency = 0x0010;
codec_configured_state_params.pres_delay_min = 0xABABAB;
codec_configured_state_params.pres_delay_max = 0xCDCDCD;
codec_configured_state_params.preferred_pres_delay_min =
@@ -1238,6 +1243,9 @@
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED);
+
+ /* Cancel is called when group goes to streaming. */
+ ASSERT_EQ(0, mock_function_count_map["alarm_cancel"]);
}
TEST_F(StateMachineTest, testConfigureCodecMulti) {
@@ -1280,6 +1288,9 @@
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED);
+
+ /* Cancel is called when group goes to streaming. */
+ ASSERT_EQ(0, mock_function_count_map["alarm_cancel"]);
}
TEST_F(StateMachineTest, testConfigureQosSingle) {
@@ -1322,6 +1333,8 @@
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED);
+
+ ASSERT_EQ(0, mock_function_count_map["alarm_cancel"]);
}
TEST_F(StateMachineTest, testConfigureQosSingleRecoverCig) {
@@ -1368,6 +1381,7 @@
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED);
+ ASSERT_EQ(0, mock_function_count_map["alarm_cancel"]);
}
TEST_F(StateMachineTest, testConfigureQosMultiple) {
@@ -1413,6 +1427,7 @@
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED);
+ ASSERT_EQ(0, mock_function_count_map["alarm_cancel"]);
}
TEST_F(StateMachineTest, testStreamSingle) {
@@ -1463,6 +1478,7 @@
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+ ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
}
TEST_F(StateMachineTest, testStreamSkipEnablingSink) {
@@ -1511,6 +1527,8 @@
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+
+ ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
}
TEST_F(StateMachineTest, testStreamSkipEnablingSinkSource) {
@@ -1562,6 +1580,7 @@
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+ ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
}
TEST_F(StateMachineTest, testStreamMultipleConversational) {
@@ -1615,6 +1634,7 @@
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+ ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
}
TEST_F(StateMachineTest, testStreamMultiple) {
@@ -1667,6 +1687,7 @@
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+ ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
}
TEST_F(StateMachineTest, testUpdateMetadataMultiple) {
@@ -1722,6 +1743,9 @@
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+ ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
+ mock_function_count_map["alarm_cancel"] = 0;
+
// Make sure all devices get the metadata update
leAudioDevice = group->GetFirstDevice();
expected_devices_written = 0;
@@ -1741,6 +1765,9 @@
ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream(
group, static_cast<LeAudioContextType>(context_type),
metadata_context_type));
+
+ /* This is just update metadata - watchdog is not used */
+ ASSERT_EQ(0, mock_function_count_map["alarm_cancel"]);
}
TEST_F(StateMachineTest, testDisableSingle) {
@@ -1781,6 +1808,11 @@
InjectInitialIdleNotification(group);
+ EXPECT_CALL(
+ mock_callbacks_,
+ StatusReportCb(leaudio_group_id,
+ bluetooth::le_audio::GroupStreamStatus::STREAMING));
+
// Start the configuration and stream Media content
LeAudioGroupStateMachine::Get()->StartStream(
group, static_cast<LeAudioContextType>(context_type),
@@ -1790,6 +1822,10 @@
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+ testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
+ ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
+ mock_function_count_map["alarm_cancel"] = 0;
+
// Validate GroupStreamStatus
EXPECT_CALL(
mock_callbacks_,
@@ -1806,6 +1842,9 @@
// Check if group has transition to a proper state
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED);
+
+ testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
+ ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
}
TEST_F(StateMachineTest, testDisableMultiple) {
@@ -1857,6 +1896,8 @@
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+ ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
+ mock_function_count_map["alarm_cancel"] = 0;
// Validate GroupStreamStatus
EXPECT_CALL(
@@ -1874,6 +1915,8 @@
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED);
+ testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
+ ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
}
TEST_F(StateMachineTest, testDisableBidirectional) {
@@ -1958,6 +2001,19 @@
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+ ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
+ mock_function_count_map["alarm_cancel"] = 0;
+
+ // Validate GroupStreamStatus
+ EXPECT_CALL(
+ mock_callbacks_,
+ StatusReportCb(leaudio_group_id,
+ bluetooth::le_audio::GroupStreamStatus::SUSPENDING));
+ EXPECT_CALL(
+ mock_callbacks_,
+ StatusReportCb(leaudio_group_id,
+ bluetooth::le_audio::GroupStreamStatus::SUSPENDED));
+
// Suspend the stream
LeAudioGroupStateMachine::Get()->SuspendStream(group);
@@ -1966,6 +2022,9 @@
types::AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED);
ASSERT_EQ(removed_bidirectional, true);
ASSERT_EQ(removed_unidirectional, true);
+
+ testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
+ ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
}
TEST_F(StateMachineTest, testReleaseSingle) {
@@ -2012,7 +2071,8 @@
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
-
+ ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
+ mock_function_count_map["alarm_cancel"] = 0;
// Validate GroupStreamStatus
EXPECT_CALL(
mock_callbacks_,
@@ -2027,6 +2087,7 @@
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
+ ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
}
TEST_F(StateMachineTest, testReleaseCachingSingle) {
@@ -2090,12 +2151,17 @@
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+ ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
+ mock_function_count_map["alarm_cancel"] = 0;
+
// Stop the stream
LeAudioGroupStateMachine::Get()->StopStream(group);
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED);
+
+ ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
}
TEST_F(StateMachineTest,
@@ -2167,6 +2233,9 @@
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+ ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
+ mock_function_count_map["alarm_cancel"] = 0;
+
// Stop the stream
LeAudioGroupStateMachine::Get()->StopStream(group);
@@ -2174,6 +2243,9 @@
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED);
+ ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
+ mock_function_count_map["alarm_cancel"] = 0;
+
// Start the configuration and stream Media content
LeAudioGroupStateMachine::Get()->StartStream(
group, static_cast<LeAudioContextType>(context_type),
@@ -2182,6 +2254,9 @@
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+
+ ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
+ mock_function_count_map["alarm_cancel"] = 0;
}
TEST_F(StateMachineTest,
@@ -2267,6 +2342,9 @@
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+ ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
+ mock_function_count_map["alarm_cancel"] = 0;
+
// Stop the stream
LeAudioGroupStateMachine::Get()->StopStream(group);
@@ -2274,6 +2352,9 @@
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED);
+ ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
+ mock_function_count_map["alarm_cancel"] = 0;
+
// Start the configuration and stream Media content
context_type = kContextTypeMedia;
LeAudioGroupStateMachine::Get()->StartStream(
@@ -2283,6 +2364,7 @@
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+ ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
}
TEST_F(StateMachineTest, testReleaseMultiple) {
@@ -2332,6 +2414,9 @@
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+ ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
+ mock_function_count_map["alarm_cancel"] = 0;
+
// Validate GroupStreamStatus
EXPECT_CALL(
mock_callbacks_,
@@ -2346,6 +2431,7 @@
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
+ ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
}
TEST_F(StateMachineTest, testReleaseBidirectional) {
@@ -2393,11 +2479,16 @@
ASSERT_EQ(group->GetState(),
types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+ ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
+ mock_function_count_map["alarm_cancel"] = 0;
+
// Stop the stream
LeAudioGroupStateMachine::Get()->StopStream(group);
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
+ ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
+ mock_function_count_map["alarm_cancel"] = 0;
}
TEST_F(StateMachineTest, testDisableAndReleaseBidirectional) {
@@ -2558,6 +2649,9 @@
/* Single disconnect as it is bidirectional Cis*/
EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(2);
+ ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
+ mock_function_count_map["alarm_cancel"] = 0;
+
for (auto* device = group->GetFirstDevice(); device != nullptr;
device = group->GetNextDevice(device)) {
for (auto& ase : device->ases_) {
@@ -2581,6 +2675,8 @@
ASSERT_EQ(ase.state, types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
}
}
+
+ ASSERT_EQ(0, mock_function_count_map["alarm_cancel"]);
}
TEST_F(StateMachineTest, testAseAutonomousRelease2Devices) {
@@ -2885,15 +2981,15 @@
uint8_t reason) {
bluetooth::hci::iso_manager::cis_disconnected_evt event;
- auto* ase = leAudioDevice->GetFirstActiveAse();
- while (ase) {
- event.reason = reason;
- event.cig_id = group->group_id_;
- event.cis_conn_hdl = ase->cis_conn_hdl;
- LeAudioGroupStateMachine::Get()->ProcessHciNotifCisDisconnected(
- group, leAudioDevice, &event);
-
- ase = leAudioDevice->GetNextActiveAse(ase);
+ for (auto const ase : leAudioDevice->ases_) {
+ if (ase.data_path_state != types::AudioStreamDataPathState::CIS_ASSIGNED &&
+ ase.data_path_state != types::AudioStreamDataPathState::IDLE) {
+ event.reason = reason;
+ event.cig_id = group->group_id_;
+ event.cis_conn_hdl = ase.cis_conn_hdl;
+ LeAudioGroupStateMachine::Get()->ProcessHciNotifCisDisconnected(
+ group, leAudioDevice, &event);
+ }
}
}
@@ -2917,6 +3013,7 @@
auto* leAudioDevice = group->GetFirstDevice();
LeAudioDevice* lastDevice;
+ LeAudioDevice* fistDevice = leAudioDevice;
auto expected_devices_written = 0;
while (leAudioDevice) {
@@ -2996,6 +3093,123 @@
auto ccids = ltv.Find(le_audio::types::kLeAudioMetadataTypeCcidList);
ASSERT_TRUE(ccids.has_value());
ASSERT_NE(std::find(ccids->begin(), ccids->end(), media_ccid), ccids->end());
+
+ /* Verify that ASE of first device are still good*/
+ auto ase = fistDevice->GetFirstActiveAse();
+ ASSERT_NE(ase->max_transport_latency, 0);
+ ASSERT_NE(ase->retrans_nb, 0);
+}
+
+TEST_F(StateMachineTest, testAttachDeviceToTheConversationalStream) {
+ const auto context_type = kContextTypeConversational;
+ const auto leaudio_group_id = 6;
+ const auto num_devices = 2;
+
+ ContentControlIdKeeper::GetInstance()->SetCcid(call_context, call_ccid);
+
+ // Prepare multiple fake connected devices in a group
+ auto* group =
+ PrepareSingleTestDeviceGroup(leaudio_group_id, context_type, num_devices);
+ ASSERT_EQ(group->Size(), num_devices);
+
+ PrepareConfigureCodecHandler(group);
+ PrepareConfigureQosHandler(group);
+ PrepareEnableHandler(group);
+ PrepareReceiverStartReady(group);
+ PrepareDisableHandler(group);
+ PrepareReleaseHandler(group);
+
+ auto* leAudioDevice = group->GetFirstDevice();
+ LeAudioDevice* lastDevice;
+ LeAudioDevice* fistDevice = leAudioDevice;
+
+ auto expected_devices_written = 0;
+ while (leAudioDevice) {
+ /* Three Writes:
+ * 1: Codec Config
+ * 2: Codec QoS
+ * 3: Enabling
+ */
+ lastDevice = leAudioDevice;
+ EXPECT_CALL(gatt_queue,
+ WriteCharacteristic(leAudioDevice->conn_id_,
+ leAudioDevice->ctp_hdls_.val_hdl, _,
+ GATT_WRITE_NO_RSP, _, _))
+ .Times(AtLeast(3));
+ expected_devices_written++;
+ leAudioDevice = group->GetNextDevice(leAudioDevice);
+ }
+ ASSERT_EQ(expected_devices_written, num_devices);
+
+ EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1);
+ EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1);
+ EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(4);
+
+ InjectInitialIdleNotification(group);
+
+ // Start the configuration and stream Conversational content
+ LeAudioGroupStateMachine::Get()->StartStream(
+ group, static_cast<LeAudioContextType>(context_type),
+ types::AudioContexts(context_type));
+
+ // Check if group has transitioned to a proper state
+ ASSERT_EQ(group->GetState(),
+ types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+ testing::Mock::VerifyAndClearExpectations(&mock_iso_manager_);
+
+ // Inject CIS and ACL disconnection of first device
+ InjectCisDisconnected(group, lastDevice, HCI_ERR_CONNECTION_TOUT);
+ InjectAclDisconnected(group, lastDevice);
+
+ // Check if group keeps streaming
+ ASSERT_EQ(group->GetState(),
+ types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+
+ lastDevice->conn_id_ = 3;
+ group->UpdateAudioContextTypeAvailability();
+
+ // Make sure ASE with disconnected CIS are not left in STREAMING
+ ASSERT_EQ(lastDevice->GetFirstAseWithState(
+ ::le_audio::types::kLeAudioDirectionSink,
+ types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING),
+ nullptr);
+ ASSERT_EQ(lastDevice->GetFirstAseWithState(
+ ::le_audio::types::kLeAudioDirectionSource,
+ types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING),
+ nullptr);
+
+ EXPECT_CALL(gatt_queue, WriteCharacteristic(lastDevice->conn_id_,
+ lastDevice->ctp_hdls_.val_hdl, _,
+ GATT_WRITE_NO_RSP, _, _))
+ .Times(AtLeast(3));
+
+ EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1);
+ EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2);
+ LeAudioGroupStateMachine::Get()->AttachToStream(group, lastDevice);
+
+ // Check if group keeps streaming
+ ASSERT_EQ(group->GetState(),
+ types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+
+ // Verify that the joining device receives the right CCID list
+ auto lastMeta = lastDevice->GetFirstActiveAse()->metadata;
+ bool parsedOk = false;
+ auto ltv = le_audio::types::LeAudioLtvMap::Parse(lastMeta.data(),
+ lastMeta.size(), parsedOk);
+ ASSERT_TRUE(parsedOk);
+
+ auto ccids = ltv.Find(le_audio::types::kLeAudioMetadataTypeCcidList);
+ ASSERT_TRUE(ccids.has_value());
+ ASSERT_NE(std::find(ccids->begin(), ccids->end(), call_ccid), ccids->end());
+
+ /* Verify that ASE of first device are still good*/
+ auto ase = fistDevice->GetFirstActiveAse();
+ ASSERT_NE(ase->max_transport_latency, 0);
+ ASSERT_NE(ase->retrans_nb, 0);
+
+ // Make sure ASEs with reconnected CIS are in STREAMING state
+ ASSERT_TRUE(lastDevice->HaveAllActiveAsesSameState(
+ types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING));
}
TEST_F(StateMachineTest, StartStreamAfterConfigure) {
@@ -3114,6 +3328,9 @@
testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
+ ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
+ mock_function_count_map["alarm_cancel"] = 0;
+
// Validate GroupStreamStatus
EXPECT_CALL(
mock_callbacks_,
@@ -3130,6 +3347,9 @@
testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
+ ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
+ mock_function_count_map["alarm_cancel"] = 0;
+
// Restart stream
EXPECT_CALL(
mock_callbacks_,
@@ -3141,6 +3361,7 @@
group, context_type, types::AudioContexts(context_type));
testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
+ ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
}
TEST_F(StateMachineTest, BoundedHeadphonesConversationalToMediaChannelCount_2) {
@@ -3298,6 +3519,9 @@
testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
+ ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
+ mock_function_count_map["alarm_cancel"] = 0;
+
// Validate GroupStreamStatus
EXPECT_CALL(
mock_callbacks_,
@@ -3313,6 +3537,8 @@
LeAudioGroupStateMachine::Get()->StopStream(group);
testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
+ ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
+ mock_function_count_map["alarm_cancel"] = 0;
// Restart stream
EXPECT_CALL(
@@ -3325,6 +3551,401 @@
group, new_context_type, types::AudioContexts(new_context_type));
testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
+ ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
}
+
+TEST_F(StateMachineTest, lateCisDisconnectedEvent_ConfiguredByUser) {
+ const auto context_type = kContextTypeMedia;
+ const auto leaudio_group_id = 6;
+ const auto num_devices = 1;
+
+ ContentControlIdKeeper::GetInstance()->SetCcid(media_context, media_ccid);
+
+ // Prepare multiple fake connected devices in a group
+ auto* group =
+ PrepareSingleTestDeviceGroup(leaudio_group_id, context_type, num_devices);
+ ASSERT_EQ(group->Size(), num_devices);
+
+ PrepareConfigureCodecHandler(group, 0, true);
+ PrepareConfigureQosHandler(group);
+ PrepareEnableHandler(group);
+ PrepareDisableHandler(group);
+ PrepareReleaseHandler(group);
+
+ auto* leAudioDevice = group->GetFirstDevice();
+ auto expected_devices_written = 0;
+
+ /* Three Writes:
+ * 1: Codec Config
+ * 2: Codec QoS
+ * 3: Enabling
+ */
+ EXPECT_CALL(gatt_queue, WriteCharacteristic(leAudioDevice->conn_id_,
+ leAudioDevice->ctp_hdls_.val_hdl,
+ _, GATT_WRITE_NO_RSP, _, _))
+ .Times(AtLeast(3));
+ expected_devices_written++;
+
+ ASSERT_EQ(expected_devices_written, num_devices);
+
+ EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1);
+ EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1);
+ EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2);
+
+ InjectInitialIdleNotification(group);
+
+ // Start the configuration and stream Media content
+ LeAudioGroupStateMachine::Get()->StartStream(
+ group, static_cast<LeAudioContextType>(context_type),
+ types::AudioContexts(context_type));
+
+ // Check if group has transitioned to a proper state
+ ASSERT_EQ(group->GetState(),
+ types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+ testing::Mock::VerifyAndClearExpectations(&mock_iso_manager_);
+
+ ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
+ mock_function_count_map["alarm_cancel"] = 0;
+
+ /* Prepare DisconnectCis mock to not symulate CisDisconnection */
+ ON_CALL(*mock_iso_manager_, DisconnectCis).WillByDefault(Return());
+
+ /* Do reconfiguration */
+ group->SetPendingConfiguration();
+
+ // Validate GroupStreamStatus
+ EXPECT_CALL(
+ mock_callbacks_,
+ StatusReportCb(leaudio_group_id,
+ bluetooth::le_audio::GroupStreamStatus::RELEASING));
+
+ EXPECT_CALL(mock_callbacks_,
+ StatusReportCb(
+ leaudio_group_id,
+ bluetooth::le_audio::GroupStreamStatus::CONFIGURED_BY_USER))
+ .Times(0);
+ LeAudioGroupStateMachine::Get()->StopStream(group);
+
+ testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
+
+ ASSERT_EQ(0, mock_function_count_map["alarm_cancel"]);
+
+ EXPECT_CALL(mock_callbacks_,
+ StatusReportCb(
+ leaudio_group_id,
+ bluetooth::le_audio::GroupStreamStatus::CONFIGURED_BY_USER));
+
+ // Inject CIS and ACL disconnection of first device
+ InjectCisDisconnected(group, leAudioDevice, HCI_ERR_CONN_CAUSE_LOCAL_HOST);
+ testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
+ ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
+}
+
+TEST_F(StateMachineTest, lateCisDisconnectedEvent_AutonomousConfigured) {
+ const auto context_type = kContextTypeMedia;
+ const auto leaudio_group_id = 6;
+ const auto num_devices = 1;
+
+ ContentControlIdKeeper::GetInstance()->SetCcid(media_context, media_ccid);
+
+ // Prepare multiple fake connected devices in a group
+ auto* group =
+ PrepareSingleTestDeviceGroup(leaudio_group_id, context_type, num_devices);
+ ASSERT_EQ(group->Size(), num_devices);
+
+ PrepareConfigureCodecHandler(group, 0, true);
+ PrepareConfigureQosHandler(group);
+ PrepareEnableHandler(group);
+ PrepareDisableHandler(group);
+ PrepareReleaseHandler(group);
+
+ auto* leAudioDevice = group->GetFirstDevice();
+ auto expected_devices_written = 0;
+
+ /* Three Writes:
+ * 1: Codec Config
+ * 2: Codec QoS
+ * 3: Enabling
+ */
+ EXPECT_CALL(gatt_queue, WriteCharacteristic(leAudioDevice->conn_id_,
+ leAudioDevice->ctp_hdls_.val_hdl,
+ _, GATT_WRITE_NO_RSP, _, _))
+ .Times(AtLeast(3));
+ expected_devices_written++;
+
+ ASSERT_EQ(expected_devices_written, num_devices);
+
+ EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1);
+ EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1);
+ EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2);
+
+ InjectInitialIdleNotification(group);
+
+ // Start the configuration and stream Media content
+ LeAudioGroupStateMachine::Get()->StartStream(
+ group, static_cast<LeAudioContextType>(context_type),
+ types::AudioContexts(context_type));
+
+ // Check if group has transitioned to a proper state
+ ASSERT_EQ(group->GetState(),
+ types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+ testing::Mock::VerifyAndClearExpectations(&mock_iso_manager_);
+
+ ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
+ mock_function_count_map["alarm_cancel"] = 0;
+
+ /* Prepare DisconnectCis mock to not symulate CisDisconnection */
+ ON_CALL(*mock_iso_manager_, DisconnectCis).WillByDefault(Return());
+
+ // Validate GroupStreamStatus
+ EXPECT_CALL(
+ mock_callbacks_,
+ StatusReportCb(leaudio_group_id,
+ bluetooth::le_audio::GroupStreamStatus::RELEASING));
+
+ EXPECT_CALL(
+ mock_callbacks_,
+ StatusReportCb(
+ leaudio_group_id,
+ bluetooth::le_audio::GroupStreamStatus::CONFIGURED_AUTONOMOUS))
+ .Times(0);
+
+ // Stop the stream
+ LeAudioGroupStateMachine::Get()->StopStream(group);
+
+ // Check if group has transitioned to a proper state
+ ASSERT_EQ(group->GetState(),
+ types::AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED);
+
+ testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
+
+ ASSERT_EQ(0, mock_function_count_map["alarm_cancel"]);
+
+ EXPECT_CALL(
+ mock_callbacks_,
+ StatusReportCb(
+ leaudio_group_id,
+ bluetooth::le_audio::GroupStreamStatus::CONFIGURED_AUTONOMOUS));
+
+ // Inject CIS and ACL disconnection of first device
+ InjectCisDisconnected(group, leAudioDevice, HCI_ERR_CONN_CAUSE_LOCAL_HOST);
+ testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
+ ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
+}
+
+TEST_F(StateMachineTest, lateCisDisconnectedEvent_Idle) {
+ const auto context_type = kContextTypeMedia;
+ const auto leaudio_group_id = 6;
+ const auto num_devices = 1;
+
+ ContentControlIdKeeper::GetInstance()->SetCcid(media_context, media_ccid);
+
+ // Prepare multiple fake connected devices in a group
+ auto* group =
+ PrepareSingleTestDeviceGroup(leaudio_group_id, context_type, num_devices);
+ ASSERT_EQ(group->Size(), num_devices);
+
+ PrepareConfigureCodecHandler(group);
+ PrepareConfigureQosHandler(group);
+ PrepareEnableHandler(group);
+ PrepareDisableHandler(group);
+ PrepareReleaseHandler(group);
+
+ auto* leAudioDevice = group->GetFirstDevice();
+ auto expected_devices_written = 0;
+
+ /* Three Writes:
+ * 1: Codec Config
+ * 2: Codec QoS
+ * 3: Enabling
+ */
+ EXPECT_CALL(gatt_queue, WriteCharacteristic(leAudioDevice->conn_id_,
+ leAudioDevice->ctp_hdls_.val_hdl,
+ _, GATT_WRITE_NO_RSP, _, _))
+ .Times(AtLeast(3));
+ expected_devices_written++;
+
+ ASSERT_EQ(expected_devices_written, num_devices);
+
+ EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1);
+ EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1);
+ EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2);
+
+ InjectInitialIdleNotification(group);
+
+ // Start the configuration and stream Media content
+ LeAudioGroupStateMachine::Get()->StartStream(
+ group, static_cast<LeAudioContextType>(context_type),
+ types::AudioContexts(context_type));
+
+ // Check if group has transitioned to a proper state
+ ASSERT_EQ(group->GetState(),
+ types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+ testing::Mock::VerifyAndClearExpectations(&mock_iso_manager_);
+
+ ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
+ mock_function_count_map["alarm_cancel"] = 0;
+ /* Prepare DisconnectCis mock to not symulate CisDisconnection */
+ ON_CALL(*mock_iso_manager_, DisconnectCis).WillByDefault(Return());
+
+ // Validate GroupStreamStatus
+ EXPECT_CALL(
+ mock_callbacks_,
+ StatusReportCb(leaudio_group_id,
+ bluetooth::le_audio::GroupStreamStatus::RELEASING));
+
+ EXPECT_CALL(mock_callbacks_,
+ StatusReportCb(leaudio_group_id,
+ bluetooth::le_audio::GroupStreamStatus::IDLE))
+ .Times(0);
+
+ // Stop the stream
+ LeAudioGroupStateMachine::Get()->StopStream(group);
+
+ // Check if group has transitioned to a proper state
+ ASSERT_EQ(group->GetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
+ ASSERT_EQ(0, mock_function_count_map["alarm_cancel"]);
+
+ testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
+
+ EXPECT_CALL(mock_callbacks_,
+ StatusReportCb(leaudio_group_id,
+ bluetooth::le_audio::GroupStreamStatus::IDLE));
+
+ // Inject CIS and ACL disconnection of first device
+ InjectCisDisconnected(group, leAudioDevice, HCI_ERR_CONN_CAUSE_LOCAL_HOST);
+ testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
+ ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
+}
+
+TEST_F(StateMachineTest, StreamReconfigureAfterCisLostTwoDevices) {
+ auto context_type = kContextTypeConversational;
+ const auto leaudio_group_id = 4;
+ const auto num_devices = 2;
+
+ // Prepare multiple fake connected devices in a group
+ auto* group = PrepareSingleTestDeviceGroup(
+ leaudio_group_id, context_type, num_devices,
+ kContextTypeConversational | kContextTypeMedia);
+ ASSERT_EQ(group->Size(), num_devices);
+
+ PrepareConfigureCodecHandler(group);
+ PrepareConfigureQosHandler(group);
+ PrepareEnableHandler(group);
+ PrepareReceiverStartReady(group);
+
+ /* Prepare DisconnectCis mock to not symulate CisDisconnection */
+ ON_CALL(*mock_iso_manager_, DisconnectCis).WillByDefault(Return());
+
+ EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(2);
+ EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(2);
+ EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(6);
+ EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(0);
+ EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(0);
+ EXPECT_CALL(*mock_iso_manager_, RemoveCig(_, _)).Times(1);
+
+ InjectInitialIdleNotification(group);
+
+ auto* leAudioDevice = group->GetFirstDevice();
+ auto expected_devices_written = 0;
+ while (leAudioDevice) {
+ EXPECT_CALL(gatt_queue,
+ WriteCharacteristic(leAudioDevice->conn_id_,
+ leAudioDevice->ctp_hdls_.val_hdl, _,
+ GATT_WRITE_NO_RSP, _, _))
+ .Times(3);
+ expected_devices_written++;
+ leAudioDevice = group->GetNextDevice(leAudioDevice);
+ }
+ ASSERT_EQ(expected_devices_written, num_devices);
+
+ // Validate GroupStreamStatus
+ EXPECT_CALL(
+ mock_callbacks_,
+ StatusReportCb(leaudio_group_id,
+ bluetooth::le_audio::GroupStreamStatus::STREAMING));
+
+ // Start the configuration and stream Media content
+ context_type = kContextTypeMedia;
+ ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream(
+ group, static_cast<LeAudioContextType>(context_type),
+ types::AudioContexts(context_type)));
+
+ // Check if group has transitioned to a proper state
+ ASSERT_EQ(group->GetState(),
+ types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+ ASSERT_EQ(1, mock_function_count_map["alarm_cancel"]);
+ testing::Mock::VerifyAndClearExpectations(&mock_iso_manager_);
+ testing::Mock::VerifyAndClearExpectations(&gatt_queue);
+ testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
+
+ // Device disconnects due to timeout of CIS
+ leAudioDevice = group->GetFirstDevice();
+ while (leAudioDevice) {
+ InjectCisDisconnected(group, leAudioDevice, HCI_ERR_CONN_CAUSE_LOCAL_HOST);
+ // Disconnect device
+ LeAudioGroupStateMachine::Get()->ProcessHciNotifAclDisconnected(
+ group, leAudioDevice);
+
+ leAudioDevice = group->GetNextDevice(leAudioDevice);
+ }
+
+ LOG(INFO) << "GK A1";
+ group->ReloadAudioLocations();
+ group->ReloadAudioDirections();
+ group->UpdateAudioContextTypeAvailability();
+
+ // Start conversational scenario
+ leAudioDevice = group->GetFirstDevice();
+ int device_cnt = num_devices;
+ while (leAudioDevice) {
+ LOG(INFO) << "GK A11";
+ leAudioDevice->conn_id_ = device_cnt--;
+ leAudioDevice->SetConnectionState(DeviceConnectState::CONNECTED);
+ leAudioDevice = group->GetNextDevice(leAudioDevice);
+ }
+
+ LOG(INFO) << "GK A2";
+ InjectInitialIdleNotification(group);
+
+ group->ReloadAudioLocations();
+ group->ReloadAudioDirections();
+ group->UpdateAudioContextTypeAvailability(kContextTypeConversational |
+ kContextTypeMedia);
+
+ leAudioDevice = group->GetFirstDevice();
+ expected_devices_written = 0;
+ while (leAudioDevice) {
+ EXPECT_CALL(gatt_queue,
+ WriteCharacteristic(leAudioDevice->conn_id_,
+ leAudioDevice->ctp_hdls_.val_hdl, _,
+ GATT_WRITE_NO_RSP, _, _))
+ .Times(4);
+ expected_devices_written++;
+ leAudioDevice = group->GetNextDevice(leAudioDevice);
+ }
+ ASSERT_EQ(expected_devices_written, num_devices);
+
+ // Validate GroupStreamStatus
+ EXPECT_CALL(
+ mock_callbacks_,
+ StatusReportCb(leaudio_group_id,
+ bluetooth::le_audio::GroupStreamStatus::STREAMING));
+
+ // Start the configuration and stream Conversational content
+ context_type = kContextTypeConversational;
+ ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream(
+ group, static_cast<LeAudioContextType>(context_type),
+ types::AudioContexts(context_type)));
+
+ // Check if group has transitioned to a proper state
+ ASSERT_EQ(group->GetState(),
+ types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+ ASSERT_EQ(2, mock_function_count_map["alarm_cancel"]);
+ testing::Mock::VerifyAndClearExpectations(&mock_iso_manager_);
+ testing::Mock::VerifyAndClearExpectations(&gatt_queue);
+ testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
+}
+
} // namespace internal
} // namespace le_audio
diff --git a/system/bta/test/bta_hf_client_add_record_test.cc b/system/bta/test/bta_hf_client_add_record_test.cc
index 5e50cc0..63b1b03 100644
--- a/system/bta/test/bta_hf_client_add_record_test.cc
+++ b/system/bta/test/bta_hf_client_add_record_test.cc
@@ -63,11 +63,11 @@
};
TEST_F(BtaHfClientAddRecordTest, test_hf_client_add_record) {
- tBTA_HF_CLIENT_FEAT features = BTIF_HF_CLIENT_FEATURES;
+ tBTA_HF_CLIENT_FEAT features = get_default_hf_client_features();
uint32_t sdp_handle = 0;
uint8_t scn = 0;
bta_hf_client_add_record("Handsfree", scn, features, sdp_handle);
- ASSERT_EQ(gVersion, BTA_HFP_VERSION);
+ ASSERT_EQ(gVersion, get_default_hfp_version());
}
diff --git a/system/bta/test/common/btif_storage_mock.cc b/system/bta/test/common/btif_storage_mock.cc
index f5d8a6f..9440394 100644
--- a/system/bta/test/common/btif_storage_mock.cc
+++ b/system/bta/test/common/btif_storage_mock.cc
@@ -110,4 +110,9 @@
uint8_t active_preset) {
LOG_ASSERT(btif_storage_interface) << "Mock storage module not set!";
btif_storage_interface->SetLeaudioHasActivePreset(address, active_preset);
+}
+
+void btif_storage_remove_leaudio_has(const RawAddress& address) {
+ LOG_ASSERT(btif_storage_interface) << "Mock storage module not set!";
+ btif_storage_interface->RemoveLeaudioHas(address);
}
\ No newline at end of file
diff --git a/system/bta/test/common/btif_storage_mock.h b/system/bta/test/common/btif_storage_mock.h
index d7e0013..77fb91d 100644
--- a/system/bta/test/common/btif_storage_mock.h
+++ b/system/bta/test/common/btif_storage_mock.h
@@ -50,6 +50,7 @@
virtual bool GetLeaudioHasPresets(const RawAddress& address,
std::vector<uint8_t>& presets_bin,
uint8_t& active_preset) = 0;
+ virtual void RemoveLeaudioHas(const RawAddress& address) = 0;
virtual ~BtifStorageInterface() = default;
};
@@ -88,6 +89,8 @@
(const RawAddress& address, uint8_t features), (override));
MOCK_METHOD((void), SetLeaudioHasActivePreset,
(const RawAddress& address, uint8_t active_preset), (override));
+ MOCK_METHOD((void), RemoveLeaudioHas, (const RawAddress& address),
+ (override));
};
/**
diff --git a/system/bta/vc/device.cc b/system/bta/vc/device.cc
index 4eb15f5..33f509b 100644
--- a/system/bta/vc/device.cc
+++ b/system/bta/vc/device.cc
@@ -29,26 +29,27 @@
using namespace bluetooth::vc::internal;
+void VolumeControlDevice::DeregisterNotifications(tGATT_IF gatt_if) {
+ if (volume_state_handle != 0)
+ BTA_GATTC_DeregisterForNotifications(gatt_if, address, volume_state_handle);
+
+ if (volume_flags_handle != 0)
+ BTA_GATTC_DeregisterForNotifications(gatt_if, address, volume_flags_handle);
+
+ for (const VolumeOffset& of : audio_offsets.volume_offsets) {
+ BTA_GATTC_DeregisterForNotifications(gatt_if, address,
+ of.audio_descr_handle);
+ BTA_GATTC_DeregisterForNotifications(gatt_if, address,
+ of.audio_location_handle);
+ BTA_GATTC_DeregisterForNotifications(gatt_if, address, of.state_handle);
+ }
+}
+
void VolumeControlDevice::Disconnect(tGATT_IF gatt_if) {
LOG(INFO) << __func__ << ": " << this->ToString();
if (IsConnected()) {
- if (volume_state_handle != 0)
- BTA_GATTC_DeregisterForNotifications(gatt_if, address,
- volume_state_handle);
-
- if (volume_flags_handle != 0)
- BTA_GATTC_DeregisterForNotifications(gatt_if, address,
- volume_flags_handle);
-
- for (const VolumeOffset& of : audio_offsets.volume_offsets) {
- BTA_GATTC_DeregisterForNotifications(gatt_if, address,
- of.audio_descr_handle);
- BTA_GATTC_DeregisterForNotifications(gatt_if, address,
- of.audio_location_handle);
- BTA_GATTC_DeregisterForNotifications(gatt_if, address, of.state_handle);
- }
-
+ DeregisterNotifications(gatt_if);
BtaGattQueue::Clean(connection_id);
BTA_GATTC_Close(connection_id);
connection_id = GATT_INVALID_CONN_ID;
diff --git a/system/bta/vc/devices.h b/system/bta/vc/devices.h
index d5126f5..321a530 100644
--- a/system/bta/vc/devices.h
+++ b/system/bta/vc/devices.h
@@ -109,6 +109,8 @@
void Disconnect(tGATT_IF gatt_if);
+ void DeregisterNotifications(tGATT_IF gatt_if);
+
bool UpdateHandles(void);
void ResetHandles(void);
diff --git a/system/bta/vc/vc.cc b/system/bta/vc/vc.cc
index fde14a9..196816e 100644
--- a/system/bta/vc/vc.cc
+++ b/system/bta/vc/vc.cc
@@ -102,6 +102,21 @@
volume_control_devices_.Add(address, true);
} else {
device->connecting_actively = true;
+
+ if (device->IsConnected()) {
+ LOG(WARNING) << __func__ << ": address=" << address
+ << ", connection_id=" << device->connection_id
+ << " already connected.";
+
+ if (device->IsReady()) {
+ callbacks_->OnConnectionState(ConnectionState::CONNECTED,
+ device->address);
+ } else {
+ OnGattConnected(GATT_SUCCESS, device->connection_id, gatt_if_,
+ device->address, BT_TRANSPORT_LE, GATT_MAX_MTU_SIZE);
+ }
+ return;
+ }
}
BTA_GATTC_Open(gatt_if_, address, BTM_BLE_DIRECT_CONNECTION, false);
@@ -182,6 +197,29 @@
}
}
+ void ClearDeviceInformationAndStartSearch(VolumeControlDevice* device) {
+ if (!device) {
+ LOG_ERROR("Device is null");
+ return;
+ }
+
+ LOG_INFO(": address=%s", device->address.ToString().c_str());
+ if (device->service_changed_rcvd) {
+ LOG_INFO("Device already is waiting for new services");
+ return;
+ }
+
+ std::vector<RawAddress> devices = {device->address};
+ device->DeregisterNotifications(gatt_if_);
+
+ RemovePendingVolumeControlOperations(devices,
+ bluetooth::groups::kGroupUnknown);
+ device->first_connection = true;
+ device->service_changed_rcvd = true;
+ BtaGattQueue::Clean(device->connection_id);
+ BTA_GATTC_ServiceSearchRequest(device->connection_id, &kVolumeControlUuid);
+ }
+
void OnServiceChangeEvent(const RawAddress& address) {
VolumeControlDevice* device =
volume_control_devices_.FindByAddress(address);
@@ -189,10 +227,8 @@
LOG(ERROR) << __func__ << "Skipping unknown device " << address;
return;
}
- LOG(INFO) << __func__ << ": address=" << address;
- device->first_connection = true;
- device->service_changed_rcvd = true;
- BtaGattQueue::Clean(device->connection_id);
+
+ ClearDeviceInformationAndStartSearch(device);
}
void OnServiceDiscDoneEvent(const RawAddress& address) {
@@ -249,7 +285,12 @@
}
if (status != GATT_SUCCESS) {
- LOG(INFO) << __func__ << ": status=" << static_cast<int>(status);
+ LOG_INFO(": status=0x%02x", static_cast<int>(status));
+ if (status == GATT_DATABASE_OUT_OF_SYNC) {
+ LOG_INFO("Database out of sync for %s",
+ device->address.ToString().c_str());
+ ClearDeviceInformationAndStartSearch(device);
+ }
return;
}
@@ -547,10 +588,15 @@
}
if (status != GATT_SUCCESS) {
- LOG(ERROR) << __func__
- << "Failed to register for notification: " << loghex(handle)
- << " status: " << status;
- device_cleanup_helper(device, true);
+ if (status == GATT_DATABASE_OUT_OF_SYNC) {
+ LOG_INFO("Database out of sync for %s, conn_id: 0x%04x",
+ device->address.ToString().c_str(), connection_id);
+ ClearDeviceInformationAndStartSearch(device);
+ } else {
+ LOG_ERROR("Failed to register for notification: 0x%04x, status 0x%02x",
+ handle, status);
+ device_cleanup_helper(device, true);
+ }
return;
}
@@ -597,13 +643,22 @@
return;
}
+ if (!device->IsConnected()) {
+ LOG(ERROR) << __func__
+ << " Skipping disconnect of the already disconnected device, "
+ "connection_id="
+ << loghex(connection_id);
+ return;
+ }
+
// If we get here, it means, device has not been exlicitly disconnected.
bool device_ready = device->IsReady();
device_cleanup_helper(device, device->connecting_actively);
if (device_ready) {
- volume_control_devices_.Add(remote_bda, true);
+ device->first_connection = true;
+ device->connecting_actively = true;
/* Add device into BG connection to accept remote initiated connection */
BTA_GATTC_Open(gatt_if_, remote_bda, BTM_BLE_BKG_CONNECT_ALLOW_LIST,
@@ -683,6 +738,12 @@
/* In case of error, remove device from the tracking operation list */
RemoveDeviceFromOperationList(device->address, PTR_TO_INT(data));
+
+ if (status == GATT_DATABASE_OUT_OF_SYNC) {
+ LOG_INFO("Database out of sync for %s",
+ device->address.ToString().c_str());
+ ClearDeviceInformationAndStartSearch(device);
+ }
}
static void operation_callback(void* data) {
@@ -1031,7 +1092,6 @@
if (notify)
callbacks_->OnConnectionState(ConnectionState::DISCONNECTED,
device->address);
- volume_control_devices_.Remove(device->address);
}
void devices_control_point_helper(std::vector<RawAddress>& devices,
diff --git a/system/bta/vc/vc_test.cc b/system/bta/vc/vc_test.cc
index 159eb80..ca1bb02 100644
--- a/system/bta/vc/vc_test.cc
+++ b/system/bta/vc/vc_test.cc
@@ -547,6 +547,55 @@
TestAppUnregister();
}
+TEST_F(VolumeControlTest, test_reconnect_after_interrupted_discovery) {
+ const RawAddress test_address = GetTestAddress(0);
+
+ // Initial connection - no callback calls yet as we want to disconnect in the
+ // middle
+ SetSampleDatabaseVOCS(1);
+ TestAppRegister();
+ TestConnect(test_address);
+ EXPECT_CALL(*callbacks,
+ OnConnectionState(ConnectionState::CONNECTED, test_address))
+ .Times(0);
+ EXPECT_CALL(*callbacks, OnDeviceAvailable(test_address, 2)).Times(0);
+ GetConnectedEvent(test_address, 1);
+ Mock::VerifyAndClearExpectations(callbacks.get());
+
+ // Remote disconnects in the middle of the service discovery
+ EXPECT_CALL(*callbacks,
+ OnConnectionState(ConnectionState::DISCONNECTED, test_address));
+ GetDisconnectedEvent(test_address, 1);
+ Mock::VerifyAndClearExpectations(callbacks.get());
+
+ // This time let the service discovery pass
+ ON_CALL(gatt_interface, ServiceSearchRequest(_, _))
+ .WillByDefault(Invoke(
+ [&](uint16_t conn_id, const bluetooth::Uuid* p_srvc_uuid) -> void {
+ if (*p_srvc_uuid == kVolumeControlUuid)
+ GetSearchCompleteEvent(conn_id);
+ }));
+
+ // Remote is being connected by another GATT client
+ EXPECT_CALL(*callbacks,
+ OnConnectionState(ConnectionState::CONNECTED, test_address));
+ EXPECT_CALL(*callbacks, OnDeviceAvailable(test_address, 2));
+ GetConnectedEvent(test_address, 1);
+ Mock::VerifyAndClearExpectations(callbacks.get());
+
+ // Request connect when the remote was already connected by another service
+ EXPECT_CALL(*callbacks, OnDeviceAvailable(test_address, 2)).Times(0);
+ EXPECT_CALL(*callbacks,
+ OnConnectionState(ConnectionState::CONNECTED, test_address));
+ VolumeControl::Get()->Connect(test_address);
+ // The GetConnectedEvent(test_address, 1); should not be triggered here, since
+ // GATT implementation will not send this event for the already connected
+ // device
+ Mock::VerifyAndClearExpectations(callbacks.get());
+
+ TestAppUnregister();
+}
+
TEST_F(VolumeControlTest, test_add_from_storage) {
TestAppRegister();
TestAddFromStorage(GetTestAddress(0), true);
@@ -754,6 +803,46 @@
TestAppUnregister();
}
+TEST_F(VolumeControlTest, test_read_vcs_database_out_of_sync) {
+ const RawAddress test_address = GetTestAddress(0);
+ EXPECT_CALL(*callbacks, OnVolumeStateChanged(test_address, _, _, false));
+ std::vector<uint16_t> handles({0x0021});
+ uint16_t conn_id = 1;
+
+ SetSampleDatabase(conn_id);
+ TestAppRegister();
+ TestConnect(test_address);
+ GetConnectedEvent(test_address, conn_id);
+
+ EXPECT_CALL(gatt_queue, ReadCharacteristic(conn_id, _, _, _))
+ .WillRepeatedly(DoDefault());
+ for (auto const& handle : handles) {
+ EXPECT_CALL(gatt_queue, ReadCharacteristic(conn_id, handle, _, _))
+ .WillOnce(DoDefault());
+ }
+ GetSearchCompleteEvent(conn_id);
+
+ /* Simulate database change on the remote side. */
+ ON_CALL(gatt_queue, WriteCharacteristic(_, _, _, _, _, _))
+ .WillByDefault(
+ Invoke([this](uint16_t conn_id, uint16_t handle,
+ std::vector<uint8_t> value, tGATT_WRITE_TYPE write_type,
+ GATT_WRITE_OP_CB cb, void* cb_data) {
+ auto* svc = gatt::FindService(services_map[conn_id], handle);
+ if (svc == nullptr) return;
+
+ tGATT_STATUS status = GATT_DATABASE_OUT_OF_SYNC;
+ if (cb)
+ cb(conn_id, status, handle, value.size(), value.data(), cb_data);
+ }));
+
+ ON_CALL(gatt_interface, ServiceSearchRequest(_, _)).WillByDefault(Return());
+ EXPECT_CALL(gatt_interface, ServiceSearchRequest(_, _));
+ VolumeControl::Get()->SetVolume(test_address, 15);
+ Mock::VerifyAndClearExpectations(&gatt_interface);
+ TestAppUnregister();
+}
+
class VolumeControlCallbackTest : public VolumeControlTest {
protected:
const RawAddress test_address = GetTestAddress(0);
diff --git a/system/btif/Android.bp b/system/btif/Android.bp
index 6299237..ad0de3b 100644
--- a/system/btif/Android.bp
+++ b/system/btif/Android.bp
@@ -176,6 +176,7 @@
"lib-bt-packets-base",
"lib-bt-packets-avrcp",
"libbt-audio-hal-interface",
+ "libcom.android.sysprop.bluetooth",
"libaudio-a2dp-hw-utils",
],
cflags: [
@@ -384,11 +385,131 @@
],
static_libs: [
"libbluetooth-types",
+ "libcom.android.sysprop.bluetooth",
"libosi",
],
cflags: ["-DBUILDCFG"],
}
+cc_test {
+ name: "net_test_btif_hh",
+ host_supported: true,
+ defaults: [
+ "fluoride_defaults",
+ "mts_defaults",
+ ],
+ test_suites: ["device-tests"],
+ include_dirs: [
+ "frameworks/av/media/libaaudio/include",
+ "packages/modules/Bluetooth/system",
+ "packages/modules/Bluetooth/system/bta/dm",
+ "packages/modules/Bluetooth/system/bta/include",
+ "packages/modules/Bluetooth/system/bta/sys",
+ "packages/modules/Bluetooth/system/btif/avrcp",
+ "packages/modules/Bluetooth/system/btif/co",
+ "packages/modules/Bluetooth/system/btif/include",
+ "packages/modules/Bluetooth/system/device/include",
+ "packages/modules/Bluetooth/system/embdrv/sbc/decoder/include",
+ "packages/modules/Bluetooth/system/embdrv/sbc/encoder/include",
+ "packages/modules/Bluetooth/system/gd",
+ "packages/modules/Bluetooth/system/include",
+ "packages/modules/Bluetooth/system/internal_include",
+ "packages/modules/Bluetooth/system/stack/a2dp",
+ "packages/modules/Bluetooth/system/stack/avdt",
+ "packages/modules/Bluetooth/system/stack/btm",
+ "packages/modules/Bluetooth/system/stack/include",
+ "packages/modules/Bluetooth/system/stack/l2cap",
+ "packages/modules/Bluetooth/system/udrv/include",
+ "packages/modules/Bluetooth/system/utils/include",
+ "packages/modules/Bluetooth/system/vnd/include",
+ "system/libfmq/include",
+ "system/libhwbinder/include",
+ ],
+ srcs: [
+ ":LibBluetoothSources",
+ ":TestCommonMainHandler",
+ ":TestCommonMockFunctions",
+ ":TestMockAndroidHardware",
+ ":BtaDmSources",
+ ":TestMockBtaAg",
+ ":TestMockBtaAr",
+ ":TestMockBtaAv",
+ ":TestMockBtaCsis",
+ ":TestMockBtaGatt",
+ ":TestMockBtaGroups",
+ ":TestMockBtaHas",
+ ":TestMockBtaHd",
+ ":TestMockBtaHearingAid",
+ ":TestMockBtaHf",
+ ":TestMockBtaHh",
+ ":TestMockBtaJv",
+ ":TestMockBtaLeAudio",
+ ":TestMockBtaLeAudioHalVerifier",
+ ":TestMockBtaPan",
+ ":TestMockBtaSdp",
+ ":TestMockBtaSys",
+ ":TestMockBtaVc",
+ ":TestMockBtu",
+ ":TestMockBtcore",
+ ":TestMockCommon",
+ ":TestMockFrameworks",
+ ":TestMockHci",
+ ":TestMockMainShim",
+ ":TestMockOsi",
+ ":TestMockStack",
+ ":TestMockSystemLibfmq",
+ ":TestMockUdrv",
+ ":TestMockUtils",
+ "test/btif_hh_test.cc",
+ ],
+ generated_headers: [
+ "BluetoothGeneratedDumpsysDataSchema_h",
+ "BluetoothGeneratedPackets_h",
+ ],
+ header_libs: ["libbluetooth_headers"],
+ shared_libs: [
+ "android.hardware.bluetooth.audio@2.0",
+ "android.hardware.bluetooth.audio@2.1",
+ "libcrypto",
+ "libcutils",
+ "libhidlbase",
+ "liblog",
+ "libtinyxml2",
+ ],
+ whole_static_libs: [
+ "libbtif",
+ ],
+ static_libs: [
+ "android.hardware.bluetooth.a2dp@1.0",
+ "avrcp-target-service",
+ "libaudio-a2dp-hw-utils",
+ "libbluetooth-types",
+ "libbt-audio-hal-interface",
+ "libbt-stack",
+ "libbtdevice",
+ "lib-bt-packets",
+ "lib-bt-packets-avrcp",
+ "lib-bt-packets-base",
+ "libcom.android.sysprop.bluetooth",
+ "libc++fs",
+ "libflatbuffers-cpp",
+ "libgmock",
+ ],
+ cflags: ["-DBUILDCFG"],
+ target: {
+ android: {
+ shared_libs: [
+ "libbinder_ndk",
+ "android.hardware.bluetooth.audio-V2-ndk",
+ ],
+ },
+ },
+ sanitize: {
+ address: true,
+ cfi: true,
+ misc_undefined: ["bounds"],
+ },
+}
// Cycle stack test
cc_test {
name: "net_test_btif_stack",
@@ -487,6 +608,7 @@
"lib-bt-packets",
"lib-bt-packets-avrcp",
"lib-bt-packets-base",
+ "libcom.android.sysprop.bluetooth",
"libc++fs",
"libflatbuffers-cpp",
"libgmock",
diff --git a/system/btif/co/bta_av_co.cc b/system/btif/co/bta_av_co.cc
index 34406f1..e828f0a 100644
--- a/system/btif/co/bta_av_co.cc
+++ b/system/btif/co/bta_av_co.cc
@@ -33,6 +33,7 @@
#include "btif/include/btif_av.h"
#include "include/hardware/bt_av.h"
#include "osi/include/osi.h" // UNUSED_ATTR
+#include "osi/include/allocator.h"
#include "stack/include/a2dp_codec_api.h"
#include "stack/include/a2dp_error_codes.h"
#include "stack/include/avdt_api.h"
@@ -1372,6 +1373,12 @@
p_buf = btif_a2dp_source_audio_readbuf();
if (p_buf == nullptr) return nullptr;
+ if (p_buf->offset < 4) {
+ osi_free(p_buf);
+ APPL_TRACE_ERROR("No space for timestamp in packet, dropped");
+ return nullptr;
+ }
+
/*
* Retrieve the timestamp information from the media packet,
* and set up the packet header.
@@ -1385,6 +1392,8 @@
!A2DP_BuildCodecHeader(p_codec_info, p_buf, p_buf->layer_specific)) {
APPL_TRACE_ERROR("%s: unsupported codec type (%d)", __func__,
A2DP_GetCodecType(p_codec_info));
+ osi_free(p_buf);
+ return nullptr;
}
if (ContentProtectEnabled() && (active_peer_ != nullptr) &&
diff --git a/system/btif/include/btif_a2dp_source.h b/system/btif/include/btif_a2dp_source.h
index f0b4b24..df20319 100644
--- a/system/btif/include/btif_a2dp_source.h
+++ b/system/btif/include/btif_a2dp_source.h
@@ -63,7 +63,7 @@
// Shutdown the A2DP Source module.
// This function should be called by the BTIF state machine to stop streaming.
-void btif_a2dp_source_shutdown(void);
+void btif_a2dp_source_shutdown(std::promise<void>);
// Cleanup the A2DP Source module.
// This function should be called by the BTIF state machine during graceful
diff --git a/system/btif/include/btif_bqr.h b/system/btif/include/btif_bqr.h
index 3905a8a..b0778ab 100644
--- a/system/btif/include/btif_bqr.h
+++ b/system/btif/include/btif_bqr.h
@@ -99,6 +99,8 @@
// Total length of all parameters of the link Quality related event except
// Vendor Specific Parameters.
static constexpr uint8_t kLinkQualityParamTotalLen = 48;
+// 7.8.116 LE Read ISO Link Quality command
+static constexpr uint8_t kISOLinkQualityParamTotalLen = 24;
// Total length of all parameters of the ROOT_INFLAMMATION event except Vendor
// Specific Parameters.
static constexpr uint8_t kRootInflammationParamTotalLen = 3;
diff --git a/system/btif/include/btif_sock.h b/system/btif/include/btif_sock.h
index cb0378e..494782e 100644
--- a/system/btif/include/btif_sock.h
+++ b/system/btif/include/btif_sock.h
@@ -18,11 +18,35 @@
#pragma once
-#include "btif_uid.h"
-
#include <hardware/bt_sock.h>
+#include "btif_uid.h"
+#include "types/raw_address.h"
+
+enum {
+ SOCKET_CONNECTION_STATE_UNKNOWN,
+ // Socket acts as a server waiting for connection
+ SOCKET_CONNECTION_STATE_LISTENING,
+ // Socket acts as a client trying to connect
+ SOCKET_CONNECTION_STATE_CONNECTING,
+ // Socket is connected
+ SOCKET_CONNECTION_STATE_CONNECTED,
+ // Socket tries to disconnect from remote
+ SOCKET_CONNECTION_STATE_DISCONNECTING,
+ // This socket is closed
+ SOCKET_CONNECTION_STATE_DISCONNECTED,
+};
+
+enum {
+ SOCKET_ROLE_UNKNOWN,
+ SOCKET_ROLE_LISTEN,
+ SOCKET_ROLE_CONNECTION,
+};
+
const btsock_interface_t* btif_sock_get_interface(void);
bt_status_t btif_sock_init(uid_set_t* uid_set);
void btif_sock_cleanup(void);
+
+void btif_sock_connection_logger(int state, int role, const RawAddress& addr);
+void btif_sock_dump(int fd);
diff --git a/system/btif/src/bluetooth.cc b/system/btif/src/bluetooth.cc
index 9a16712..9e089f7 100644
--- a/system/btif/src/bluetooth.cc
+++ b/system/btif/src/bluetooth.cc
@@ -58,6 +58,7 @@
#include "bta/include/bta_le_audio_broadcaster_api.h"
#include "bta/include/bta_vc_api.h"
#include "btif/avrcp/avrcp_service.h"
+#include "btif/include/btif_sock.h"
#include "btif/include/stack_manager.h"
#include "btif_a2dp.h"
#include "btif_activity_attribution.h"
@@ -434,6 +435,7 @@
btif_debug_av_dump(fd);
bta_debug_av_dump(fd);
stack_debug_avdtp_api_dump(fd);
+ btif_sock_dump(fd);
bluetooth::avrcp::AvrcpService::DebugDump(fd);
btif_debug_config_dump(fd);
BTA_HfClientDumpStatistics(fd);
diff --git a/system/btif/src/btif_a2dp_source.cc b/system/btif/src/btif_a2dp_source.cc
index f13abac..4ca2c8c 100644
--- a/system/btif/src/btif_a2dp_source.cc
+++ b/system/btif/src/btif_a2dp_source.cc
@@ -30,6 +30,7 @@
#include <string.h>
#include <algorithm>
+#include <future>
#include "audio_a2dp_hw/include/audio_a2dp_hw.h"
#include "audio_hal_interface/a2dp_encoding.h"
@@ -242,7 +243,7 @@
const RawAddress& peer_address, std::promise<void> start_session_promise);
static void btif_a2dp_source_end_session_delayed(
const RawAddress& peer_address);
-static void btif_a2dp_source_shutdown_delayed(void);
+static void btif_a2dp_source_shutdown_delayed(std::promise<void>);
static void btif_a2dp_source_cleanup_delayed(void);
static void btif_a2dp_source_audio_tx_start_event(void);
static void btif_a2dp_source_audio_tx_stop_event(void);
@@ -483,7 +484,7 @@
}
}
-void btif_a2dp_source_shutdown(void) {
+void btif_a2dp_source_shutdown(std::promise<void> shutdown_complete_promise) {
LOG_INFO("%s: state=%s", __func__, btif_a2dp_source_cb.StateStr().c_str());
if ((btif_a2dp_source_cb.State() == BtifA2dpSource::kStateOff) ||
@@ -495,10 +496,12 @@
btif_a2dp_source_cb.SetState(BtifA2dpSource::kStateShuttingDown);
btif_a2dp_source_thread.DoInThread(
- FROM_HERE, base::Bind(&btif_a2dp_source_shutdown_delayed));
+ FROM_HERE, base::BindOnce(&btif_a2dp_source_shutdown_delayed,
+ std::move(shutdown_complete_promise)));
}
-static void btif_a2dp_source_shutdown_delayed(void) {
+static void btif_a2dp_source_shutdown_delayed(
+ std::promise<void> shutdown_complete_promise) {
LOG_INFO("%s: state=%s", __func__, btif_a2dp_source_cb.StateStr().c_str());
// Stop the timer
@@ -514,13 +517,16 @@
btif_a2dp_source_cb.tx_audio_queue = nullptr;
btif_a2dp_source_cb.SetState(BtifA2dpSource::kStateOff);
+
+ shutdown_complete_promise.set_value();
}
void btif_a2dp_source_cleanup(void) {
LOG_INFO("%s: state=%s", __func__, btif_a2dp_source_cb.StateStr().c_str());
// Make sure the source is shutdown
- btif_a2dp_source_shutdown();
+ std::promise<void> shutdown_complete_promise;
+ btif_a2dp_source_shutdown(std::move(shutdown_complete_promise));
btif_a2dp_source_thread.DoInThread(
FROM_HERE, base::Bind(&btif_a2dp_source_cleanup_delayed));
diff --git a/system/btif/src/btif_av.cc b/system/btif/src/btif_av.cc
index 4e132c6..b05ed16 100644
--- a/system/btif/src/btif_av.cc
+++ b/system/btif/src/btif_av.cc
@@ -26,6 +26,7 @@
#include <frameworks/proto_logging/stats/enums/bluetooth/a2dp/enums.pb.h>
#include <frameworks/proto_logging/stats/enums/bluetooth/enums.pb.h>
+#include <chrono>
#include <cstdint>
#include <future>
#include <memory>
@@ -52,6 +53,7 @@
#include "main/shim/dumpsys.h"
#include "osi/include/allocator.h"
#include "osi/include/properties.h"
+#include "stack/include/avrc_api.h"
#include "stack/include/bt_hdr.h"
#include "stack/include/btm_api.h"
#include "stack/include/btu.h" // do_in_main_thread
@@ -500,7 +502,15 @@
<< ": unable to set active peer to empty in BtaAvCo";
}
btif_a2dp_source_end_session(active_peer_);
- btif_a2dp_source_shutdown();
+ std::promise<void> shutdown_complete_promise;
+ std::future<void> shutdown_complete_future =
+ shutdown_complete_promise.get_future();
+ btif_a2dp_source_shutdown(std::move(shutdown_complete_promise));
+ using namespace std::chrono_literals;
+ if (shutdown_complete_future.wait_for(1s) ==
+ std::future_status::timeout) {
+ LOG_ERROR("Timed out waiting for A2DP source shutdown to complete.");
+ }
active_peer_ = peer_address;
peer_ready_promise.set_value();
return true;
@@ -3332,9 +3342,10 @@
features |= BTA_AV_FEAT_DELAY_RPT;
}
-#if (AVRC_ADV_CTRL_INCLUDED == TRUE)
- features |= BTA_AV_FEAT_RCCT | BTA_AV_FEAT_ADV_CTRL | BTA_AV_FEAT_BROWSE;
-#endif
+ if (avrcp_absolute_volume_is_enabled()) {
+ features |= BTA_AV_FEAT_RCCT | BTA_AV_FEAT_ADV_CTRL | BTA_AV_FEAT_BROWSE;
+ }
+
BTA_AvEnable(features, bta_av_source_callback);
btif_av_source.RegisterAllBtaHandles();
return BT_STATUS_SUCCESS;
diff --git a/system/btif/src/btif_bqr.cc b/system/btif/src/btif_bqr.cc
index a728d80..03f1a25 100644
--- a/system/btif/src/btif_bqr.cc
+++ b/system/btif/src/btif_bqr.cc
@@ -77,13 +77,27 @@
STREAM_TO_UINT32(bqr_link_quality_event_.buffer_underflow_bytes, p_param_buf);
if (vendor_cap_supported_version >= kBqrIsoVersion) {
- STREAM_TO_UINT32(bqr_link_quality_event_.tx_total_packets, p_param_buf);
- STREAM_TO_UINT32(bqr_link_quality_event_.tx_unacked_packets, p_param_buf);
- STREAM_TO_UINT32(bqr_link_quality_event_.tx_flushed_packets, p_param_buf);
- STREAM_TO_UINT32(bqr_link_quality_event_.tx_last_subevent_packets,
- p_param_buf);
- STREAM_TO_UINT32(bqr_link_quality_event_.crc_error_packets, p_param_buf);
- STREAM_TO_UINT32(bqr_link_quality_event_.rx_duplicate_packets, p_param_buf);
+ if (length < kLinkQualityParamTotalLen + kISOLinkQualityParamTotalLen) {
+ LOG(WARNING) << __func__
+ << ": Parameter total length: " << std::to_string(length)
+ << " is abnormal. "
+ << "vendor_cap_supported_version: "
+ << vendor_cap_supported_version << " "
+ << " (>= "
+ << "kBqrIsoVersion=" << kBqrIsoVersion << "), "
+ << "It should not be shorter than: "
+ << std::to_string(kLinkQualityParamTotalLen +
+ kISOLinkQualityParamTotalLen);
+ } else {
+ STREAM_TO_UINT32(bqr_link_quality_event_.tx_total_packets, p_param_buf);
+ STREAM_TO_UINT32(bqr_link_quality_event_.tx_unacked_packets, p_param_buf);
+ STREAM_TO_UINT32(bqr_link_quality_event_.tx_flushed_packets, p_param_buf);
+ STREAM_TO_UINT32(bqr_link_quality_event_.tx_last_subevent_packets,
+ p_param_buf);
+ STREAM_TO_UINT32(bqr_link_quality_event_.crc_error_packets, p_param_buf);
+ STREAM_TO_UINT32(bqr_link_quality_event_.rx_duplicate_packets,
+ p_param_buf);
+ }
}
const auto now = system_clock::to_time_t(system_clock::now());
diff --git a/system/btif/src/btif_config.cc b/system/btif/src/btif_config.cc
index 4b99365..a777ba8 100644
--- a/system/btif/src/btif_config.cc
+++ b/system/btif/src/btif_config.cc
@@ -112,7 +112,7 @@
metrics_salt.fill(0);
}
if (!AddressObfuscator::IsSaltValid(metrics_salt)) {
- LOG(INFO) << __func__ << ": Metrics salt is not invalid, creating new one";
+ LOG(INFO) << __func__ << ": Metrics salt is invalid, creating new one";
if (RAND_bytes(metrics_salt.data(), metrics_salt.size()) != 1) {
LOG(FATAL) << __func__ << "Failed to generate salt for metrics";
}
diff --git a/system/btif/src/btif_dm.cc b/system/btif/src/btif_dm.cc
index 16981d6..9b0569d 100644
--- a/system/btif/src/btif_dm.cc
+++ b/system/btif/src/btif_dm.cc
@@ -1381,6 +1381,17 @@
status = btif_storage_set_remote_addr_type(&bdaddr, addr_type);
ASSERTC(status == BT_STATUS_SUCCESS,
"failed to save remote addr type (inquiry)", status);
+
+ bool restrict_report = osi_property_get_bool(
+ "bluetooth.restrict_discovered_device.enabled", false);
+ if (restrict_report &&
+ p_search_data->inq_res.device_type == BT_DEVICE_TYPE_BLE &&
+ !(p_search_data->inq_res.ble_evt_type & BTM_BLE_CONNECTABLE_MASK)) {
+ LOG_INFO("%s: Ble device is not connectable",
+ bdaddr.ToString().c_str());
+ break;
+ }
+
/* Callback to notify upper layer of device */
invoke_device_found_cb(num_properties, properties);
}
@@ -1626,6 +1637,14 @@
}
pairing_cb.gatt_over_le =
btif_dm_pairing_cb_t::ServiceDiscoveryState::FINISHED;
+
+ if (pairing_cb.sdp_over_classic !=
+ btif_dm_pairing_cb_t::ServiceDiscoveryState::SCHEDULED) {
+ // Both SDP and bonding are either done, or not scheduled,
+ // we are safe to clear the service discovery part of CB.
+ LOG_INFO("clearing pairing_cb");
+ pairing_cb = {};
+ }
}
} else {
LOG_INFO("New GATT over SDP UUIDs for %s:", PRIVATE_ADDRESS(bd_addr));
@@ -1694,18 +1713,6 @@
/* Send the event to the BTIF */
invoke_remote_device_properties_cb(BT_STATUS_SUCCESS, bd_addr,
num_properties, prop);
-
- if ((bd_addr == pairing_cb.bd_addr ||
- bd_addr == pairing_cb.static_bdaddr) &&
- pairing_cb.sdp_over_classic !=
- btif_dm_pairing_cb_t::ServiceDiscoveryState::SCHEDULED &&
- pairing_cb.gatt_over_le !=
- btif_dm_pairing_cb_t::ServiceDiscoveryState::SCHEDULED) {
- // Both SDP and bonding are either done, or not scheduled, we are safe
- // to clear the service discovery part of CB.
- LOG_INFO("clearing pairing_cb");
- pairing_cb = {};
- }
} break;
default: { ASSERTC(0, "unhandled search services event", event); } break;
@@ -2792,7 +2799,11 @@
stop_oob_advertiser();
}
waiting_on_oob_advertiser_start = true;
- SMP_CrLocScOobData();
+ if (!SMP_CrLocScOobData()) {
+ waiting_on_oob_advertiser_start = false;
+ invoke_oob_data_request_cb(transport, false, Octet16{}, Octet16{},
+ RawAddress{}, 0x00);
+ }
} else {
invoke_oob_data_request_cb(transport, false, Octet16{}, Octet16{},
RawAddress{}, 0x00);
diff --git a/system/btif/src/btif_gatt.cc b/system/btif/src/btif_gatt.cc
index d8076b0..2d0944f 100644
--- a/system/btif/src/btif_gatt.cc
+++ b/system/btif/src/btif_gatt.cc
@@ -55,6 +55,7 @@
******************************************************************************/
static bt_status_t btif_gatt_init(const btgatt_callbacks_t* callbacks) {
bt_gatt_callbacks = callbacks;
+ BTA_GATTS_InitBonded();
return BT_STATUS_SUCCESS;
}
diff --git a/system/btif/src/btif_gatt_util.cc b/system/btif/src/btif_gatt_util.cc
index 290c431..55788b0 100644
--- a/system/btif/src/btif_gatt_util.cc
+++ b/system/btif/src/btif_gatt_util.cc
@@ -36,6 +36,7 @@
#include "btif_gatt.h"
#include "btif_storage.h"
#include "btif_util.h"
+#include "gd/os/system_properties.h"
#include "osi/include/allocator.h"
#include "osi/include/osi.h"
#include "stack/btm/btm_sec.h"
@@ -75,6 +76,12 @@
void btif_gatt_check_encrypted_link(RawAddress bd_addr,
tBT_TRANSPORT transport_link) {
+ static const bool check_encrypted = bluetooth::os::GetSystemPropertyBool(
+ "bluetooth.gatt.check_encrypted_link.enabled", true);
+ if (!check_encrypted) {
+ LOG_DEBUG("Check skipped due to system config");
+ return;
+ }
tBTM_LE_PENC_KEYS key;
if ((btif_storage_get_ble_bonding_key(
bd_addr, BTM_LE_KEY_PENC, (uint8_t*)&key,
diff --git a/system/btif/src/btif_hd.cc b/system/btif/src/btif_hd.cc
index 0d5b365..c41713a 100644
--- a/system/btif/src/btif_hd.cc
+++ b/system/btif/src/btif_hd.cc
@@ -32,10 +32,12 @@
#include "bt_target.h" // Must be first to define build configuration
#include "bta/include/bta_hd_api.h"
+#include "bta/sys/bta_sys.h"
#include "btif/include/btif_common.h"
#include "btif/include/btif_hd.h"
#include "btif/include/btif_storage.h"
#include "btif/include/btif_util.h"
+#include "gd/common/init_flags.h"
#include "include/hardware/bt_hd.h"
#include "osi/include/allocator.h"
#include "osi/include/compat.h"
@@ -162,6 +164,7 @@
BTIF_TRACE_DEBUG("%s: status=%d", __func__, p_data->status);
btif_hd_cb.status = BTIF_HD_DISABLED;
if (btif_hd_cb.service_dereg_active) {
+ bta_sys_deregister(BTA_ID_HD);
BTIF_TRACE_WARNING("registering hid host now");
btif_hh_service_registration(TRUE);
btif_hd_cb.service_dereg_active = FALSE;
@@ -181,6 +184,7 @@
addr = NULL;
}
+ LOG_INFO("Registering HID device app");
btif_hd_cb.app_registered = TRUE;
HAL_CBACK(bt_hd_callbacks, application_state_cb, addr,
BTHD_APP_STATE_REGISTERED);
@@ -192,7 +196,10 @@
BTHD_APP_STATE_NOT_REGISTERED);
if (btif_hd_cb.service_dereg_active) {
BTIF_TRACE_WARNING("disabling hid device service now");
- btif_hd_free_buf();
+ if (!bluetooth::common::init_flags::
+ delay_hidh_cleanup_until_hidh_ready_start_is_enabled()) {
+ btif_hd_free_buf();
+ }
BTA_HdDisable();
}
break;
diff --git a/system/btif/src/btif_hearing_aid.cc b/system/btif/src/btif_hearing_aid.cc
index 3b51ede..7820f42 100644
--- a/system/btif/src/btif_hearing_aid.cc
+++ b/system/btif/src/btif_hearing_aid.cc
@@ -85,30 +85,26 @@
void Connect(const RawAddress& address) override {
DVLOG(2) << __func__ << " address: " << address;
- do_in_main_thread(FROM_HERE, Bind(&HearingAid::Connect,
- Unretained(HearingAid::Get()), address));
+ do_in_main_thread(FROM_HERE, Bind(&HearingAid::Connect, address));
}
void Disconnect(const RawAddress& address) override {
DVLOG(2) << __func__ << " address: " << address;
- do_in_main_thread(FROM_HERE, Bind(&HearingAid::Disconnect,
- Unretained(HearingAid::Get()), address));
+ do_in_main_thread(FROM_HERE, Bind(&HearingAid::Disconnect, address));
do_in_jni_thread(FROM_HERE, Bind(&btif_storage_set_hearing_aid_acceptlist,
address, false));
}
void AddToAcceptlist(const RawAddress& address) override {
VLOG(2) << __func__ << " address: " << address;
- do_in_main_thread(FROM_HERE, Bind(&HearingAid::AddToAcceptlist,
- Unretained(HearingAid::Get()), address));
+ do_in_main_thread(FROM_HERE, Bind(&HearingAid::AddToAcceptlist, address));
do_in_jni_thread(FROM_HERE, Bind(&btif_storage_set_hearing_aid_acceptlist,
address, true));
}
void SetVolume(int8_t volume) override {
DVLOG(2) << __func__ << " volume: " << +volume;
- do_in_main_thread(FROM_HERE, Bind(&HearingAid::SetVolume,
- Unretained(HearingAid::Get()), volume));
+ do_in_main_thread(FROM_HERE, Bind(&HearingAid::SetVolume, volume));
}
void RemoveDevice(const RawAddress& address) override {
@@ -116,9 +112,7 @@
// RemoveDevice can be called on devices that don't have HA enabled
if (HearingAid::IsHearingAidRunning()) {
- do_in_main_thread(FROM_HERE,
- Bind(&HearingAid::Disconnect,
- Unretained(HearingAid::Get()), address));
+ do_in_main_thread(FROM_HERE, Bind(&HearingAid::Disconnect, address));
}
do_in_jni_thread(FROM_HERE,
diff --git a/system/btif/src/btif_hf.cc b/system/btif/src/btif_hf.cc
index 19f32e6..4d5aa1e 100644
--- a/system/btif/src/btif_hf.cc
+++ b/system/btif/src/btif_hf.cc
@@ -27,6 +27,10 @@
#define LOG_TAG "bt_btif_hf"
+#ifdef OS_ANDROID
+#include <hfp.sysprop.h>
+#endif
+
#include <cstdint>
#include <string>
@@ -64,25 +68,14 @@
#define BTIF_HFAG_SERVICE_NAME ("Handsfree Gateway")
#endif
-#ifndef BTIF_HF_SERVICES
-#define BTIF_HF_SERVICES (BTA_HSP_SERVICE_MASK | BTA_HFP_SERVICE_MASK)
-#endif
-
#ifndef BTIF_HF_SERVICE_NAMES
#define BTIF_HF_SERVICE_NAMES \
{ BTIF_HSAG_SERVICE_NAME, BTIF_HFAG_SERVICE_NAME }
#endif
-#ifndef BTIF_HF_FEATURES
-#define BTIF_HF_FEATURES \
- (BTA_AG_FEAT_3WAY | BTA_AG_FEAT_ECNR | BTA_AG_FEAT_REJECT | \
- BTA_AG_FEAT_ECS | BTA_AG_FEAT_EXTERR | BTA_AG_FEAT_VREC | \
- BTA_AG_FEAT_CODEC | BTA_AG_FEAT_HF_IND | BTA_AG_FEAT_ESCO_S4 | \
- BTA_AG_FEAT_UNAT)
-#endif
-
+static uint32_t get_hf_features();
/* HF features supported at runtime */
-static uint32_t btif_hf_features = BTIF_HF_FEATURES;
+static uint32_t btif_hf_features = get_hf_features();
#define BTIF_HF_INVALID_IDX (-1)
@@ -145,6 +138,36 @@
return !active_bda.IsEmpty() && active_bda == bd_addr;
}
+static tBTA_SERVICE_MASK get_BTIF_HF_SERVICES() {
+#ifdef OS_ANDROID
+ static const tBTA_SERVICE_MASK hf_services =
+ android::sysprop::bluetooth::Hfp::hf_services().value_or(
+ BTA_HSP_SERVICE_MASK | BTA_HFP_SERVICE_MASK);
+ return hf_services;
+#else
+ return BTA_HSP_SERVICE_MASK | BTA_HFP_SERVICE_MASK;
+#endif
+}
+
+/* HF features supported at runtime */
+static uint32_t get_hf_features() {
+#define DEFAULT_BTIF_HF_FEATURES \
+ (BTA_AG_FEAT_3WAY | BTA_AG_FEAT_ECNR | BTA_AG_FEAT_REJECT | \
+ BTA_AG_FEAT_ECS | BTA_AG_FEAT_EXTERR | BTA_AG_FEAT_VREC | \
+ BTA_AG_FEAT_CODEC | BTA_AG_FEAT_HF_IND | BTA_AG_FEAT_ESCO_S4 | \
+ BTA_AG_FEAT_UNAT)
+#ifdef OS_ANDROID
+ static const uint32_t hf_features =
+ android::sysprop::bluetooth::Hfp::hf_features().value_or(
+ DEFAULT_BTIF_HF_FEATURES);
+ return hf_features;
+#elif TARGET_FLOSS
+ return BTA_AG_FEAT_ECS | BTA_AG_FEAT_CODEC;
+#else
+ return DEFAULT_BTIF_HF_FEATURES;
+#endif
+}
+
/*******************************************************************************
*
* Function is_connected
@@ -773,11 +796,11 @@
// Invoke the enable service API to the core to set the appropriate service_id
// Internally, the HSP_SERVICE_ID shall also be enabled if HFP is enabled
// (phone) otherwise only HSP is enabled (tablet)
-#if (defined(BTIF_HF_SERVICES) && (BTIF_HF_SERVICES & BTA_HFP_SERVICE_MASK))
- btif_enable_service(BTA_HFP_SERVICE_ID);
-#else
- btif_enable_service(BTA_HSP_SERVICE_ID);
-#endif
+ if (get_BTIF_HF_SERVICES() & BTA_HFP_SERVICE_MASK) {
+ btif_enable_service(BTA_HFP_SERVICE_ID);
+ } else {
+ btif_enable_service(BTA_HSP_SERVICE_ID);
+ }
return BT_STATUS_SUCCESS;
}
@@ -1401,15 +1424,16 @@
btif_queue_cleanup(UUID_SERVCLASS_AG_HANDSFREE);
tBTA_SERVICE_MASK mask = btif_get_enabled_services_mask();
-#if (defined(BTIF_HF_SERVICES) && (BTIF_HF_SERVICES & BTA_HFP_SERVICE_MASK))
- if ((mask & (1 << BTA_HFP_SERVICE_ID)) != 0) {
- btif_disable_service(BTA_HFP_SERVICE_ID);
+ if (get_BTIF_HF_SERVICES() & BTA_HFP_SERVICE_MASK) {
+ if ((mask & (1 << BTA_HFP_SERVICE_ID)) != 0) {
+ btif_disable_service(BTA_HFP_SERVICE_ID);
+ }
+ } else {
+ if ((mask & (1 << BTA_HSP_SERVICE_ID)) != 0) {
+ btif_disable_service(BTA_HSP_SERVICE_ID);
+ }
}
-#else
- if ((mask & (1 << BTA_HSP_SERVICE_ID)) != 0) {
- btif_disable_service(BTA_HSP_SERVICE_ID);
- }
-#endif
+
do_in_jni_thread(FROM_HERE, base::Bind([]() { bt_hf_callbacks = nullptr; }));
}
@@ -1466,7 +1490,8 @@
/* Enable and register with BTA-AG */
BTA_AgEnable(bte_hf_evt);
for (uint8_t app_id = 0; app_id < btif_max_hf_clients; app_id++) {
- BTA_AgRegister(BTIF_HF_SERVICES, btif_hf_features, service_names, app_id);
+ BTA_AgRegister(get_BTIF_HF_SERVICES(), btif_hf_features, service_names,
+ app_id);
}
} else {
/* De-register AG */
diff --git a/system/btif/src/btif_hf_client.cc b/system/btif/src/btif_hf_client.cc
index 0826368..ed94e0d 100644
--- a/system/btif/src/btif_hf_client.cc
+++ b/system/btif/src/btif_hf_client.cc
@@ -68,13 +68,6 @@
#define BTIF_HF_CLIENT_SERVICE_NAME ("Handsfree")
#endif
-#ifndef BTIF_HF_CLIENT_FEATURES
-#define BTIF_HF_CLIENT_FEATURES \
- (BTA_HF_CLIENT_FEAT_ECNR | BTA_HF_CLIENT_FEAT_3WAY | \
- BTA_HF_CLIENT_FEAT_CLI | BTA_HF_CLIENT_FEAT_VREC | BTA_HF_CLIENT_FEAT_VOL | \
- BTA_HF_CLIENT_FEAT_ECS | BTA_HF_CLIENT_FEAT_ECC | BTA_HF_CLIENT_FEAT_CODEC)
-#endif
-
/*******************************************************************************
* Local type definitions
******************************************************************************/
@@ -313,9 +306,7 @@
* The handle is valid until we have called BTA_HfClientClose or the LL
* has notified us of channel close due to remote closing, error etc.
*/
- BTA_HfClientOpen(cb->peer_bda, &cb->handle);
-
- return BT_STATUS_SUCCESS;
+ return BTA_HfClientOpen(cb->peer_bda, &cb->handle);
}
static bt_status_t connect(RawAddress* bd_addr) {
@@ -360,7 +351,7 @@
CHECK_BTHF_CLIENT_SLC_CONNECTED(cb);
- if ((BTIF_HF_CLIENT_FEATURES & BTA_HF_CLIENT_FEAT_CODEC) &&
+ if ((get_default_hf_client_features() & BTA_HF_CLIENT_FEAT_CODEC) &&
(cb->peer_feat & BTA_HF_CLIENT_PEER_CODEC)) {
BTA_HfClientSendAT(cb->handle, BTA_HF_CLIENT_AT_CMD_BCC, 0, 0, NULL);
} else {
@@ -745,6 +736,27 @@
return BT_STATUS_SUCCESS;
}
+/*******************************************************************************
+ *
+ * Function send_hfp_audio_policy
+ *
+ * Description Send requested audio policies to remote device.
+ *
+ * Returns bt_status_t
+ *
+ ******************************************************************************/
+static bt_status_t send_android_at(const RawAddress* bd_addr, const char* arg) {
+ btif_hf_client_cb_t* cb = btif_hf_client_get_cb_by_bda(*bd_addr);
+ if (cb == NULL || !is_connected(cb)) return BT_STATUS_FAIL;
+
+ CHECK_BTHF_CLIENT_SLC_CONNECTED(cb);
+
+ BTIF_TRACE_EVENT("%s: val1 %s", __func__, arg);
+ BTA_HfClientSendAT(cb->handle, BTA_HF_CLIENT_AT_CMD_ANDROID, 0, 0, arg);
+
+ return BT_STATUS_SUCCESS;
+}
+
static const bthf_client_interface_t bthfClientInterface = {
.size = sizeof(bthf_client_interface_t),
.init = init,
@@ -765,6 +777,7 @@
.request_last_voice_tag_number = request_last_voice_tag_number,
.cleanup = cleanup,
.send_at_cmd = send_at_cmd,
+ .send_android_at = send_android_at,
};
static void process_ind_evt(tBTA_HF_CLIENT_IND* ind) {
@@ -852,6 +865,7 @@
cb->state = BTHF_CLIENT_CONNECTION_STATE_CONNECTED;
cb->peer_feat = 0;
cb->chld_feat = 0;
+ cb->handle = p_data->open.handle;
} else if (cb->state == BTHF_CLIENT_CONNECTION_STATE_CONNECTING) {
cb->state = BTHF_CLIENT_CONNECTION_STATE_DISCONNECTED;
} else {
@@ -897,6 +911,22 @@
cb->peer_bda = RawAddress::kAny;
cb->peer_feat = 0;
cb->chld_feat = 0;
+ cb->handle = 0;
+
+ /* Clean up any btif_hf_client_cb for the same disconnected bd_addr.
+ * when there is an Incoming hf_client connection is in progress and
+ * at the same time, outgoing hf_client connection is initiated then
+ * due to race condition two btif_hf_client_cb is created. This creates
+ * problem for successive connections
+ */
+ while ((cb = btif_hf_client_get_cb_by_bda(p_data->bd_addr)) != NULL) {
+ cb->state = BTHF_CLIENT_CONNECTION_STATE_DISCONNECTED;
+ cb->peer_bda = RawAddress::kAny;
+ cb->peer_feat = 0;
+ cb->chld_feat = 0;
+ cb->handle = 0;
+ }
+
btif_queue_advance();
break;
@@ -1048,8 +1078,8 @@
bt_status_t btif_hf_client_execute_service(bool b_enable) {
BTIF_TRACE_EVENT("%s: enable: %d", __func__, b_enable);
- tBTA_HF_CLIENT_FEAT features = BTIF_HF_CLIENT_FEATURES;
- uint16_t hfp_version = BTA_HFP_VERSION;
+ tBTA_HF_CLIENT_FEAT features = get_default_hf_client_features();
+ uint16_t hfp_version = get_default_hfp_version();
if (hfp_version >= HFP_VERSION_1_7) {
features |= BTA_HF_CLIENT_FEAT_ESCO_S4;
}
diff --git a/system/btif/src/btif_hh.cc b/system/btif/src/btif_hh.cc
index 3280a95..38986b9 100644
--- a/system/btif/src/btif_hh.cc
+++ b/system/btif/src/btif_hh.cc
@@ -538,6 +538,15 @@
(btif_hh_cb.status == BTIF_HH_DEV_CONNECTING)) {
btif_hh_cb.status = (BTIF_HH_STATUS)BTIF_HH_DEV_DISCONNECTED;
btif_hh_cb.pending_conn_address = RawAddress::kEmpty;
+
+ /* need to notify up-layer device is disconnected to avoid
+ * state out of sync with up-layer */
+ do_in_jni_thread(base::Bind(
+ [](RawAddress bd_addrcb) {
+ HAL_CBACK(bt_hh_callbacks, connection_state_cb, &bd_addrcb,
+ BTHH_CONN_STATE_DISCONNECTED);
+ },
+ *bd_addr));
}
return BT_STATUS_FAIL;
}
diff --git a/system/btif/src/btif_pan.cc b/system/btif/src/btif_pan.cc
index df71187..99be952 100644
--- a/system/btif/src/btif_pan.cc
+++ b/system/btif/src/btif_pan.cc
@@ -34,6 +34,9 @@
#include <linux/if_ether.h>
#include <linux/if_tun.h>
#include <net/if.h>
+#ifdef OS_ANDROID
+#include <pan.sysprop.h>
+#endif
#include <poll.h>
#include <sys/ioctl.h>
#include <unistd.h>
@@ -93,6 +96,16 @@
const btpan_interface_t* btif_pan_get_interface() { return &pan_if; }
+static bool pan_nap_is_enabled() {
+#ifdef OS_ANDROID
+ // replace build time config PAN_NAP_DISABLED with runtime
+ static const bool nap_is_enabled =
+ android::sysprop::bluetooth::Pan::nap().value_or(true);
+ return nap_is_enabled;
+#else
+ return true;
+#endif
+}
/*******************************************************************************
**
** Function btif_pan_init
@@ -118,9 +131,9 @@
btpan_cb.enabled = 1;
int role = BTPAN_ROLE_NONE;
-#if PAN_NAP_DISABLED == FALSE
- role |= BTPAN_ROLE_PANNAP;
-#endif
+ if (pan_nap_is_enabled()) {
+ role |= BTPAN_ROLE_PANNAP;
+ }
#if PANU_DISABLED == FALSE
role |= BTPAN_ROLE_PANU;
#endif
diff --git a/system/btif/src/btif_rc.cc b/system/btif/src/btif_rc.cc
index d66c427..9666668 100644
--- a/system/btif/src/btif_rc.cc
+++ b/system/btif/src/btif_rc.cc
@@ -599,22 +599,23 @@
rc_features = (btrc_remote_features_t)(rc_features | BTRC_FEAT_BROWSE);
}
-#if (AVRC_ADV_CTRL_INCLUDED == TRUE)
+ if (p_dev->rc_features & BTA_AV_FEAT_METADATA) {
+ rc_features = (btrc_remote_features_t)(rc_features | BTRC_FEAT_METADATA);
+ }
+
+ if (!avrcp_absolute_volume_is_enabled()) {
+ return;
+ }
+
if ((p_dev->rc_features & BTA_AV_FEAT_ADV_CTRL) &&
(p_dev->rc_features & BTA_AV_FEAT_RCTG)) {
rc_features =
(btrc_remote_features_t)(rc_features | BTRC_FEAT_ABSOLUTE_VOLUME);
}
-#endif
-
- if (p_dev->rc_features & BTA_AV_FEAT_METADATA) {
- rc_features = (btrc_remote_features_t)(rc_features | BTRC_FEAT_METADATA);
- }
BTIF_TRACE_DEBUG("%s: rc_features: 0x%x", __func__, rc_features);
HAL_CBACK(bt_rc_callbacks, remote_features_cb, p_dev->rc_addr, rc_features);
-#if (AVRC_ADV_CTRL_INCLUDED == TRUE)
BTIF_TRACE_DEBUG(
"%s: Checking for feature flags in btif_rc_handler with label: %d",
__func__, p_dev->rc_vol_label);
@@ -640,7 +641,6 @@
register_volumechange(p_dev->rc_vol_label, p_dev);
}
}
-#endif
}
/***************************************************************************
@@ -1959,6 +1959,11 @@
dump_rc_notification_event_id(event_id));
std::unique_lock<std::mutex> lock(btif_rc_cb.lock);
+ if (event_id > MAX_RC_NOTIFICATIONS) {
+ BTIF_TRACE_ERROR("Invalid event id");
+ return BT_STATUS_PARM_INVALID;
+ }
+
memset(&(avrc_rsp.reg_notif), 0, sizeof(tAVRC_REG_NOTIF_RSP));
avrc_rsp.reg_notif.event_id = event_id;
@@ -3650,29 +3655,31 @@
* for standard attributes.
*/
p_app_settings->num_ext_attrs = 0;
- for (xx = 0; xx < p_app_settings->ext_attr_index; xx++) {
+ for (xx = 0;
+ xx < p_app_settings->ext_attr_index && xx < AVRC_MAX_APP_ATTR_SIZE;
+ xx++) {
osi_free_and_reset((void**)&p_app_settings->ext_attrs[xx].p_str);
}
p_app_settings->ext_attr_index = 0;
- if (p_dev) {
- for (xx = 0; xx < p_app_settings->num_attrs; xx++) {
- attrs[xx] = p_app_settings->attrs[xx].attr_id;
- }
-
- do_in_jni_thread(
- FROM_HERE,
- base::Bind(bt_rc_ctrl_callbacks->playerapplicationsetting_cb,
- p_dev->rc_addr, p_app_settings->num_attrs,
- p_app_settings->attrs, 0, nullptr));
- get_player_app_setting_cmd(xx, attrs, p_dev);
+ for (xx = 0; xx < p_app_settings->num_attrs && xx < AVRC_MAX_APP_ATTR_SIZE;
+ xx++) {
+ attrs[xx] = p_app_settings->attrs[xx].attr_id;
}
+
+ do_in_jni_thread(
+ FROM_HERE, base::Bind(bt_rc_ctrl_callbacks->playerapplicationsetting_cb,
+ p_dev->rc_addr, p_app_settings->num_attrs,
+ p_app_settings->attrs, 0, nullptr));
+ get_player_app_setting_cmd(xx, attrs, p_dev);
+
return;
}
for (xx = 0; xx < p_rsp->num_attr; xx++) {
uint8_t x;
- for (x = 0; x < p_app_settings->num_ext_attrs; x++) {
+ for (x = 0; x < p_app_settings->num_ext_attrs && x < AVRC_MAX_APP_ATTR_SIZE;
+ x++) {
if (p_app_settings->ext_attrs[x].attr_id == p_rsp->p_attrs[xx].attr_id) {
p_app_settings->ext_attrs[x].charset_id = p_rsp->p_attrs[xx].charset_id;
p_app_settings->ext_attrs[x].str_len = p_rsp->p_attrs[xx].str_len;
@@ -3682,7 +3689,9 @@
}
}
- for (xx = 0; xx < p_app_settings->ext_attrs[0].num_val; xx++) {
+ for (xx = 0;
+ xx < p_app_settings->ext_attrs[0].num_val && xx < BTRC_MAX_APP_ATTR_SIZE;
+ xx++) {
vals[xx] = p_app_settings->ext_attrs[0].ext_attr_val[xx].val;
}
get_player_app_setting_value_text_cmd(vals, xx, p_dev);
@@ -3726,11 +3735,13 @@
* for standard attributes.
*/
p_app_settings->num_ext_attrs = 0;
- for (xx = 0; xx < p_app_settings->ext_attr_index; xx++) {
+ for (xx = 0;
+ xx < p_app_settings->ext_attr_index && xx < AVRC_MAX_APP_ATTR_SIZE;
+ xx++) {
int x;
btrc_player_app_ext_attr_t* p_ext_attr = &p_app_settings->ext_attrs[xx];
- for (x = 0; x < p_ext_attr->num_val; x++)
+ for (x = 0; x < p_ext_attr->num_val && x < BTRC_MAX_APP_ATTR_SIZE; x++)
osi_free_and_reset((void**)&p_ext_attr->ext_attr_val[x].p_str);
p_ext_attr->num_val = 0;
osi_free_and_reset((void**)&p_app_settings->ext_attrs[xx].p_str);
@@ -3749,11 +3760,17 @@
return;
}
+ if (p_app_settings->ext_val_index >= AVRC_MAX_APP_ATTR_SIZE) {
+ BTIF_TRACE_ERROR("ext_val_index is 0x%02x, overflow!",
+ p_app_settings->ext_val_index);
+ return;
+ }
+
for (xx = 0; xx < p_rsp->num_attr; xx++) {
uint8_t x;
btrc_player_app_ext_attr_t* p_ext_attr;
p_ext_attr = &p_app_settings->ext_attrs[p_app_settings->ext_val_index];
- for (x = 0; x < p_rsp->num_attr; x++) {
+ for (x = 0; x < p_rsp->num_attr && x < BTRC_MAX_APP_ATTR_SIZE; x++) {
if (p_ext_attr->ext_attr_val[x].val == p_rsp->p_attrs[xx].attr_id) {
p_ext_attr->ext_attr_val[x].charset_id = p_rsp->p_attrs[xx].charset_id;
p_ext_attr->ext_attr_val[x].str_len = p_rsp->p_attrs[xx].str_len;
@@ -3806,10 +3823,12 @@
**************************************************************************/
static void cleanup_app_attr_val_txt_response(
btif_rc_player_app_settings_t* p_app_settings) {
- for (uint8_t xx = 0; xx < p_app_settings->ext_attr_index; xx++) {
+ for (uint8_t xx = 0;
+ xx < p_app_settings->ext_attr_index && xx < AVRC_MAX_APP_ATTR_SIZE;
+ xx++) {
int x;
btrc_player_app_ext_attr_t* p_ext_attr = &p_app_settings->ext_attrs[xx];
- for (x = 0; x < p_ext_attr->num_val; x++) {
+ for (x = 0; x < p_ext_attr->num_val && x < BTRC_MAX_APP_ATTR_SIZE; x++) {
osi_free_and_reset((void**)&p_ext_attr->ext_attr_val[x].p_str);
}
p_ext_attr->num_val = 0;
diff --git a/system/btif/src/btif_sdp_server.cc b/system/btif/src/btif_sdp_server.cc
index 5bfd3e8..64dab16 100644
--- a/system/btif/src/btif_sdp_server.cc
+++ b/system/btif/src/btif_sdp_server.cc
@@ -287,6 +287,10 @@
bt_status_t remove_sdp_record(int record_id) {
int handle;
+ if (record_id >= MAX_SDP_SLOTS) {
+ return BT_STATUS_PARM_INVALID;
+ }
+
bluetooth_sdp_record* record;
bluetooth_sdp_types sdp_type = SDP_TYPE_RAW;
{
@@ -349,9 +353,9 @@
BTIF_TRACE_DEBUG("Sdp Server %s", __func__);
const sdp_slot_t* sdp_slot = start_create_sdp(id);
tBTA_SERVICE_ID service_id = -1;
+ bluetooth_sdp_record* record;
/* In the case we are shutting down, sdp_slot is NULL */
- if (sdp_slot != NULL) {
- bluetooth_sdp_record* record = sdp_slot->record_data;
+ if (sdp_slot != nullptr && (record = sdp_slot->record_data) != nullptr) {
int handle = -1;
switch (record->hdr.type) {
case SDP_TYPE_MAP_MAS:
diff --git a/system/btif/src/btif_sock.cc b/system/btif/src/btif_sock.cc
index b945f4b..bcbce01 100644
--- a/system/btif/src/btif_sock.cc
+++ b/system/btif/src/btif_sock.cc
@@ -18,10 +18,13 @@
#define LOG_TAG "bt_btif_sock"
+#include "btif/include/btif_sock.h"
+
#include <base/logging.h>
#include <frameworks/proto_logging/stats/enums/bluetooth/enums.pb.h>
#include <hardware/bluetooth.h>
#include <hardware/bt_sock.h>
+#include <time.h>
#include <atomic>
@@ -60,6 +63,22 @@
static std::atomic_int thread_handle{-1};
static thread_t* thread;
+#define SOCK_LOGGER_SIZE_MAX 16
+
+struct SockConnectionEvent {
+ bool used;
+ RawAddress addr;
+ int state;
+ int role;
+ struct timespec timestamp;
+
+ void dump(const int fd);
+};
+
+static std::atomic<uint8_t> logger_index;
+
+static SockConnectionEvent connection_logger[SOCK_LOGGER_SIZE_MAX];
+
const btsock_interface_t* btif_sock_get_interface(void) {
static btsock_interface_t interface = {
sizeof(interface), btsock_listen, /* listen */
@@ -131,6 +150,88 @@
thread = NULL;
}
+void btif_sock_connection_logger(int state, int role, const RawAddress& addr) {
+ LOG_INFO("address=%s, role=%d, state=%d", addr.ToString().c_str(), state,
+ role);
+
+ uint8_t index = logger_index++ % SOCK_LOGGER_SIZE_MAX;
+
+ connection_logger[index] = {
+ .used = true,
+ .addr = addr,
+ .state = state,
+ .role = role,
+ };
+ clock_gettime(CLOCK_REALTIME, &connection_logger[index].timestamp);
+}
+
+void btif_sock_dump(int fd) {
+ dprintf(fd, "\nSocket Events: \n");
+ dprintf(fd, " Time \tAddress \tState \tRole\n");
+
+ const uint8_t head = logger_index.load() % SOCK_LOGGER_SIZE_MAX;
+
+ uint8_t index = head;
+ do {
+ connection_logger[index].dump(fd);
+
+ index++;
+ index %= SOCK_LOGGER_SIZE_MAX;
+ } while (index != head);
+ dprintf(fd, "\n");
+}
+
+void SockConnectionEvent::dump(const int fd) {
+ if (!used) {
+ return;
+ }
+
+ char eventtime[20];
+ char temptime[20];
+ struct tm* tstamp = localtime(×tamp.tv_sec);
+ strftime(temptime, sizeof(temptime), "%H:%M:%S", tstamp);
+ snprintf(eventtime, sizeof(eventtime), "%s.%03ld", temptime,
+ timestamp.tv_nsec / 1000000);
+
+ const char* str_state;
+ switch (state) {
+ case SOCKET_CONNECTION_STATE_LISTENING:
+ str_state = "STATE_LISTENING";
+ break;
+ case SOCKET_CONNECTION_STATE_CONNECTING:
+ str_state = "STATE_CONNECTING";
+ break;
+ case SOCKET_CONNECTION_STATE_CONNECTED:
+ str_state = "STATE_CONNECTED";
+ break;
+ case SOCKET_CONNECTION_STATE_DISCONNECTING:
+ str_state = "STATE_DISCONNECTING";
+ break;
+ case SOCKET_CONNECTION_STATE_DISCONNECTED:
+ str_state = "STATE_DISCONNECTED";
+ break;
+ default:
+ str_state = "STATE_UNKNOWN";
+ break;
+ }
+
+ const char* str_role;
+ switch (role) {
+ case SOCKET_ROLE_LISTEN:
+ str_role = "ROLE_LISTEN";
+ break;
+ case SOCKET_ROLE_CONNECTION:
+ str_role = "ROLE_CONNECTION";
+ break;
+ default:
+ str_role = "ROLE_UNKNOWN";
+ break;
+ }
+
+ dprintf(fd, " %s\t%s\t%s \t%s\n", eventtime,
+ addr.ToString().c_str(), str_state, str_role);
+}
+
static bt_status_t btsock_listen(btsock_type_t type, const char* service_name,
const Uuid* service_uuid, int channel,
int* sock_fd, int flags, int app_uid) {
@@ -142,6 +243,8 @@
bt_status_t status = BT_STATUS_FAIL;
int original_channel = channel;
+ btif_sock_connection_logger(SOCKET_CONNECTION_STATE_LISTENING,
+ SOCKET_ROLE_LISTEN, RawAddress::kEmpty);
log_socket_connection_state(RawAddress::kEmpty, 0, type,
android::bluetooth::SocketConnectionstateEnum::
SOCKET_CONNECTION_STATE_LISTENING,
@@ -183,6 +286,8 @@
break;
}
if (status != BT_STATUS_SUCCESS) {
+ btif_sock_connection_logger(SOCKET_CONNECTION_STATE_DISCONNECTED,
+ SOCKET_ROLE_LISTEN, RawAddress::kEmpty);
log_socket_connection_state(RawAddress::kEmpty, 0, type,
android::bluetooth::SocketConnectionstateEnum::
SOCKET_CONNECTION_STATE_DISCONNECTED,
@@ -198,9 +303,13 @@
CHECK(bd_addr != NULL);
CHECK(sock_fd != NULL);
+ LOG_INFO("%s", __func__);
+
*sock_fd = INVALID_FD;
bt_status_t status = BT_STATUS_FAIL;
+ btif_sock_connection_logger(SOCKET_CONNECTION_STATE_CONNECTING,
+ SOCKET_ROLE_CONNECTION, *bd_addr);
log_socket_connection_state(*bd_addr, 0, type,
android::bluetooth::SocketConnectionstateEnum::
SOCKET_CONNECTION_STATE_CONNECTING,
@@ -245,6 +354,8 @@
break;
}
if (status != BT_STATUS_SUCCESS) {
+ btif_sock_connection_logger(SOCKET_CONNECTION_STATE_DISCONNECTED,
+ SOCKET_ROLE_CONNECTION, *bd_addr);
log_socket_connection_state(*bd_addr, 0, type,
android::bluetooth::SocketConnectionstateEnum::
SOCKET_CONNECTION_STATE_DISCONNECTED,
diff --git a/system/btif/src/btif_sock_l2cap.cc b/system/btif/src/btif_sock_l2cap.cc
index 9d5a5bd..8f42316 100644
--- a/system/btif/src/btif_sock_l2cap.cc
+++ b/system/btif/src/btif_sock_l2cap.cc
@@ -15,6 +15,7 @@
* limitations under the License.
*/
+#include <base/logging.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>
@@ -24,6 +25,7 @@
#include "bta/include/bta_jv_api.h"
#include "btif/include/btif_metrics_logging.h"
+#include "btif/include/btif_sock.h"
#include "btif/include/btif_sock_thread.h"
#include "btif/include/btif_sock_util.h"
#include "btif/include/btif_uid.h"
@@ -37,8 +39,6 @@
#include "stack/include/bt_types.h"
#include "types/raw_address.h"
-#include <base/logging.h>
-
struct packet {
struct packet *next, *prev;
uint32_t len;
@@ -206,6 +206,10 @@
if (!t) /* prever double-frees */
return;
+ btif_sock_connection_logger(
+ SOCKET_CONNECTION_STATE_DISCONNECTED,
+ sock->server ? SOCKET_ROLE_LISTEN : SOCKET_ROLE_CONNECTION, sock->addr);
+
// Whenever a socket is freed, the connection must be dropped
log_socket_connection_state(
sock->addr, sock->id, sock->is_le_coc ? BTSOCK_L2CAP_LE : BTSOCK_L2CAP,
@@ -389,6 +393,10 @@
sock->handle = p_start->handle;
+ btif_sock_connection_logger(
+ SOCKET_CONNECTION_STATE_LISTENING,
+ sock->server ? SOCKET_ROLE_LISTEN : SOCKET_ROLE_CONNECTION, sock->addr);
+
log_socket_connection_state(
sock->addr, sock->id, sock->is_le_coc ? BTSOCK_L2CAP_LE : BTSOCK_L2CAP,
android::bluetooth::SocketConnectionstateEnum::
@@ -452,6 +460,11 @@
accept_rs->id = sock->id;
sock->id = new_listen_id;
+ btif_sock_connection_logger(
+ SOCKET_CONNECTION_STATE_CONNECTED,
+ accept_rs->server ? SOCKET_ROLE_LISTEN : SOCKET_ROLE_CONNECTION,
+ accept_rs->addr);
+
log_socket_connection_state(
accept_rs->addr, accept_rs->id,
accept_rs->is_le_coc ? BTSOCK_L2CAP_LE : BTSOCK_L2CAP,
@@ -492,6 +505,10 @@
return;
}
+ btif_sock_connection_logger(
+ SOCKET_CONNECTION_STATE_CONNECTED,
+ sock->server ? SOCKET_ROLE_LISTEN : SOCKET_ROLE_CONNECTION, sock->addr);
+
log_socket_connection_state(
sock->addr, sock->id, sock->is_le_coc ? BTSOCK_L2CAP_LE : BTSOCK_L2CAP,
android::bluetooth::SOCKET_CONNECTION_STATE_CONNECTED, 0, 0,
@@ -544,6 +561,10 @@
return;
}
+ btif_sock_connection_logger(
+ SOCKET_CONNECTION_STATE_DISCONNECTING,
+ sock->server ? SOCKET_ROLE_LISTEN : SOCKET_ROLE_CONNECTION, sock->addr);
+
log_socket_connection_state(
sock->addr, sock->id, sock->is_le_coc ? BTSOCK_L2CAP_LE : BTSOCK_L2CAP,
android::bluetooth::SOCKET_CONNECTION_STATE_DISCONNECTING, 0, 0,
diff --git a/system/btif/src/btif_sock_rfc.cc b/system/btif/src/btif_sock_rfc.cc
index 7452f86..0a6a26e 100644
--- a/system/btif/src/btif_sock_rfc.cc
+++ b/system/btif/src/btif_sock_rfc.cc
@@ -31,6 +31,7 @@
#include "btif/include/btif_metrics_logging.h"
/* The JV interface can have only one user, hence we need to call a few
* L2CAP functions from this file. */
+#include "btif/include/btif_sock.h"
#include "btif/include/btif_sock_l2cap.h"
#include "btif/include/btif_sock_sdp.h"
#include "btif/include/btif_sock_thread.h"
@@ -404,6 +405,10 @@
if (slot->fd != INVALID_FD) {
shutdown(slot->fd, SHUT_RDWR);
close(slot->fd);
+ btif_sock_connection_logger(
+ SOCKET_CONNECTION_STATE_DISCONNECTED,
+ slot->f.server ? SOCKET_ROLE_LISTEN : SOCKET_ROLE_CONNECTION,
+ slot->addr);
log_socket_connection_state(
slot->addr, slot->id, BTSOCK_RFCOMM,
android::bluetooth::SOCKET_CONNECTION_STATE_DISCONNECTED,
@@ -485,6 +490,10 @@
if (p_start->status == BTA_JV_SUCCESS) {
slot->rfc_handle = p_start->handle;
+ btif_sock_connection_logger(
+ SOCKET_CONNECTION_STATE_LISTENING,
+ slot->f.server ? SOCKET_ROLE_LISTEN : SOCKET_ROLE_CONNECTION,
+ slot->addr);
log_socket_connection_state(
slot->addr, slot->id, BTSOCK_RFCOMM,
android::bluetooth::SocketConnectionstateEnum::
@@ -508,6 +517,10 @@
srv_rs, &p_open->rem_bda, p_open->handle, p_open->new_listen_handle);
if (!accept_rs) return 0;
+ btif_sock_connection_logger(
+ SOCKET_CONNECTION_STATE_CONNECTED,
+ accept_rs->f.server ? SOCKET_ROLE_LISTEN : SOCKET_ROLE_CONNECTION,
+ accept_rs->addr);
log_socket_connection_state(
accept_rs->addr, accept_rs->id, BTSOCK_RFCOMM,
android::bluetooth::SOCKET_CONNECTION_STATE_CONNECTED, 0, 0,
@@ -540,6 +553,9 @@
slot->rfc_port_handle = BTA_JvRfcommGetPortHdl(p_open->handle);
slot->addr = p_open->rem_bda;
+ btif_sock_connection_logger(
+ SOCKET_CONNECTION_STATE_CONNECTED,
+ slot->f.server ? SOCKET_ROLE_LISTEN : SOCKET_ROLE_CONNECTION, slot->addr);
log_socket_connection_state(
slot->addr, slot->id, BTSOCK_RFCOMM,
android::bluetooth::SOCKET_CONNECTION_STATE_CONNECTED, 0, 0,
diff --git a/system/btif/test/btif_hf_client_service_test.cc b/system/btif/test/btif_hf_client_service_test.cc
index db48af1..5a8863b 100644
--- a/system/btif/test/btif_hf_client_service_test.cc
+++ b/system/btif/test/btif_hf_client_service_test.cc
@@ -2,11 +2,42 @@
#include <gtest/gtest.h>
#include "bta_hfp_api.h"
+#ifdef OS_ANDROID
+#include <hfp.sysprop.h>
+#endif
+
#undef LOG_TAG
#include "btif/src/btif_hf_client.cc"
static tBTA_HF_CLIENT_FEAT gFeatures;
+#define DEFAULT_BTA_HFP_VERSION HFP_VERSION_1_7
+int get_default_hfp_version() {
+#ifdef OS_ANDROID
+ static const int version =
+ android::sysprop::bluetooth::Hfp::version().value_or(
+ DEFAULT_BTA_HFP_VERSION);
+ return version;
+#else
+ return DEFAULT_BTA_HFP_VERSION;
+#endif
+}
+
+int get_default_hf_client_features() {
+#define DEFAULT_BTIF_HF_CLIENT_FEATURES \
+ (BTA_HF_CLIENT_FEAT_ECNR | BTA_HF_CLIENT_FEAT_3WAY | \
+ BTA_HF_CLIENT_FEAT_CLI | BTA_HF_CLIENT_FEAT_VREC | BTA_HF_CLIENT_FEAT_VOL | \
+ BTA_HF_CLIENT_FEAT_ECS | BTA_HF_CLIENT_FEAT_ECC | BTA_HF_CLIENT_FEAT_CODEC)
+
+#ifdef OS_ANDROID
+ static const int features =
+ android::sysprop::bluetooth::Hfp::hf_client_features().value_or(
+ DEFAULT_BTIF_HF_CLIENT_FEATURES);
+ return features;
+#else
+ return DEFAULT_BTIF_HF_CLIENT_FEATURES;
+#endif
+}
uint8_t btif_trace_level = BT_TRACE_LEVEL_WARNING;
void LogMsg(uint32_t trace_set_mask, const char* fmt_str, ...) {}
@@ -29,9 +60,7 @@
class BtifHfClientTest : public ::testing::Test {
protected:
- void SetUp() override {
- gFeatures = BTIF_HF_CLIENT_FEATURES;
- }
+ void SetUp() override { gFeatures = get_default_hf_client_features(); }
void TearDown() override {}
};
@@ -41,5 +70,5 @@
btif_hf_client_execute_service(enable);
ASSERT_EQ((gFeatures & BTA_HF_CLIENT_FEAT_ESCO_S4) > 0,
- BTA_HFP_VERSION >= HFP_VERSION_1_7);
+ get_default_hfp_version() >= HFP_VERSION_1_7);
}
diff --git a/system/btif/test/btif_hh_test.cc b/system/btif/test/btif_hh_test.cc
new file mode 100644
index 0000000..9c1fc9d
--- /dev/null
+++ b/system/btif/test/btif_hh_test.cc
@@ -0,0 +1,284 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "btif/include/btif_hh.h"
+
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <array>
+#include <future>
+#include <vector>
+
+#include "bta/hh/bta_hh_int.h"
+#include "bta/include/bta_ag_api.h"
+#include "bta/include/bta_hh_api.h"
+#include "btcore/include/module.h"
+#include "btif/include/btif_api.h"
+#include "btif/include/stack_manager.h"
+#include "include/hardware/bt_hh.h"
+#include "test/common/mock_functions.h"
+#include "test/mock/mock_osi_allocator.h"
+
+using namespace std::chrono_literals;
+
+void set_hal_cbacks(bt_callbacks_t* callbacks);
+
+uint8_t appl_trace_level = BT_TRACE_LEVEL_DEBUG;
+uint8_t btif_trace_level = BT_TRACE_LEVEL_DEBUG;
+uint8_t btu_trace_level = BT_TRACE_LEVEL_DEBUG;
+
+module_t bt_utils_module;
+module_t gd_controller_module;
+module_t gd_idle_module;
+module_t gd_shim_module;
+module_t osi_module;
+
+const tBTA_AG_RES_DATA tBTA_AG_RES_DATA::kEmpty = {};
+
+extern void bte_hh_evt(tBTA_HH_EVT event, tBTA_HH* p_data);
+extern const bthh_interface_t* btif_hh_get_interface();
+
+namespace test {
+namespace mock {
+extern bool bluetooth_shim_is_gd_stack_started_up;
+}
+} // namespace test
+
+#if __GLIBC__
+size_t strlcpy(char* dst, const char* src, size_t siz) {
+ char* d = dst;
+ const char* s = src;
+ size_t n = siz;
+
+ /* Copy as many bytes as will fit */
+ if (n != 0) {
+ while (--n != 0) {
+ if ((*d++ = *s++) == '\0') break;
+ }
+ }
+
+ /* Not enough room in dst, add NUL and traverse rest of src */
+ if (n == 0) {
+ if (siz != 0) *d = '\0'; /* NUL-terminate dst */
+ while (*s++)
+ ;
+ }
+
+ return (s - src - 1); /* count does not include NUL */
+}
+
+pid_t gettid(void) throw() { return syscall(SYS_gettid); }
+#endif
+
+namespace {
+std::array<uint8_t, 32> data32 = {
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b,
+ 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
+ 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
+};
+
+const RawAddress kDeviceAddress({0x11, 0x22, 0x33, 0x44, 0x55, 0x66});
+const uint16_t kHhHandle = 123;
+
+// Callback parameters grouped into a structure
+struct get_report_cb_t {
+ RawAddress raw_address;
+ bthh_status_t status;
+ std::vector<uint8_t> data;
+} get_report_cb_;
+
+// Globals allow usage within function pointers
+std::promise<bt_cb_thread_evt> g_thread_evt_promise;
+std::promise<bt_status_t> g_status_promise;
+std::promise<get_report_cb_t> g_bthh_callbacks_get_report_promise;
+
+} // namespace
+
+bt_callbacks_t bt_callbacks = {
+ .size = sizeof(bt_callbacks_t),
+ .adapter_state_changed_cb = nullptr, // adapter_state_changed_callback
+ .adapter_properties_cb = nullptr, // adapter_properties_callback
+ .remote_device_properties_cb =
+ nullptr, // remote_device_properties_callback
+ .device_found_cb = nullptr, // device_found_callback
+ .discovery_state_changed_cb = nullptr, // discovery_state_changed_callback
+ .pin_request_cb = nullptr, // pin_request_callback
+ .ssp_request_cb = nullptr, // ssp_request_callback
+ .bond_state_changed_cb = nullptr, // bond_state_changed_callback
+ .address_consolidate_cb = nullptr, // address_consolidate_callback
+ .le_address_associate_cb = nullptr, // le_address_associate_callback
+ .acl_state_changed_cb = nullptr, // acl_state_changed_callback
+ .thread_evt_cb = nullptr, // callback_thread_event
+ .dut_mode_recv_cb = nullptr, // dut_mode_recv_callback
+ .le_test_mode_cb = nullptr, // le_test_mode_callback
+ .energy_info_cb = nullptr, // energy_info_callback
+ .link_quality_report_cb = nullptr, // link_quality_report_callback
+ .generate_local_oob_data_cb = nullptr, // generate_local_oob_data_callback
+ .switch_buffer_size_cb = nullptr, // switch_buffer_size_callback
+ .switch_codec_cb = nullptr, // switch_codec_callback
+};
+
+bthh_callbacks_t bthh_callbacks = {
+ .size = sizeof(bthh_callbacks_t),
+ .connection_state_cb = nullptr, // bthh_connection_state_callback
+ .hid_info_cb = nullptr, // bthh_hid_info_callback
+ .protocol_mode_cb = nullptr, // bthh_protocol_mode_callback
+ .idle_time_cb = nullptr, // bthh_idle_time_callback
+ .get_report_cb = nullptr, // bthh_get_report_callback
+ .virtual_unplug_cb = nullptr, // bthh_virtual_unplug_callback
+ .handshake_cb = nullptr, // bthh_handshake_callback
+};
+
+class BtifHhWithMockTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ reset_mock_function_count_map();
+ test::mock::osi_allocator::osi_malloc.body = [](size_t size) {
+ return malloc(size);
+ };
+ test::mock::osi_allocator::osi_calloc.body = [](size_t size) {
+ return calloc(1UL, size);
+ };
+ test::mock::osi_allocator::osi_free.body = [](void* ptr) { free(ptr); };
+ test::mock::osi_allocator::osi_free_and_reset.body = [](void** ptr) {
+ free(*ptr);
+ *ptr = nullptr;
+ };
+ }
+
+ void TearDown() override {
+ test::mock::osi_allocator::osi_malloc = {};
+ test::mock::osi_allocator::osi_calloc = {};
+ test::mock::osi_allocator::osi_free = {};
+ test::mock::osi_allocator::osi_free_and_reset = {};
+ }
+};
+
+class BtifHhWithHalCallbacksTest : public BtifHhWithMockTest {
+ protected:
+ void SetUp() override {
+ bluetooth::common::InitFlags::SetAllForTesting();
+ BtifHhWithMockTest::SetUp();
+ g_thread_evt_promise = std::promise<bt_cb_thread_evt>();
+ auto future = g_thread_evt_promise.get_future();
+ bt_callbacks.thread_evt_cb = [](bt_cb_thread_evt evt) {
+ g_thread_evt_promise.set_value(evt);
+ };
+ set_hal_cbacks(&bt_callbacks);
+ // Start the jni callback thread
+ ASSERT_EQ(BT_STATUS_SUCCESS, btif_init_bluetooth());
+ ASSERT_EQ(std::future_status::ready, future.wait_for(2s));
+ ASSERT_EQ(ASSOCIATE_JVM, future.get());
+
+ bt_callbacks.thread_evt_cb = [](bt_cb_thread_evt evt) {};
+ }
+
+ void TearDown() override {
+ g_thread_evt_promise = std::promise<bt_cb_thread_evt>();
+ auto future = g_thread_evt_promise.get_future();
+ bt_callbacks.thread_evt_cb = [](bt_cb_thread_evt evt) {
+ g_thread_evt_promise.set_value(evt);
+ };
+ // Shutdown the jni callback thread
+ ASSERT_EQ(BT_STATUS_SUCCESS, btif_cleanup_bluetooth());
+ ASSERT_EQ(std::future_status::ready, future.wait_for(2s));
+ ASSERT_EQ(DISASSOCIATE_JVM, future.get());
+
+ bt_callbacks.thread_evt_cb = [](bt_cb_thread_evt evt) {};
+ BtifHhWithMockTest::TearDown();
+ }
+};
+
+class BtifHhAdapterReady : public BtifHhWithHalCallbacksTest {
+ protected:
+ void SetUp() override {
+ BtifHhWithHalCallbacksTest::SetUp();
+ test::mock::bluetooth_shim_is_gd_stack_started_up = true;
+ ASSERT_EQ(BT_STATUS_SUCCESS,
+ btif_hh_get_interface()->init(&bthh_callbacks));
+ }
+
+ void TearDown() override {
+ test::mock::bluetooth_shim_is_gd_stack_started_up = false;
+ BtifHhWithHalCallbacksTest::TearDown();
+ }
+};
+
+class BtifHhWithDevice : public BtifHhAdapterReady {
+ protected:
+ void SetUp() override {
+ BtifHhAdapterReady::SetUp();
+
+ // Short circuit a connected device
+ btif_hh_cb.devices[0].bd_addr = kDeviceAddress;
+ btif_hh_cb.devices[0].dev_status = BTHH_CONN_STATE_CONNECTED;
+ btif_hh_cb.devices[0].dev_handle = kHhHandle;
+ }
+
+ void TearDown() override { BtifHhAdapterReady::TearDown(); }
+};
+
+TEST_F(BtifHhAdapterReady, lifecycle) {}
+
+TEST_F(BtifHhWithDevice, BTA_HH_GET_RPT_EVT) {
+ tBTA_HH data = {
+ .hs_data =
+ {
+ .status = BTA_HH_OK,
+ .handle = kHhHandle,
+ .rsp_data =
+ {
+ .p_rpt_data = static_cast<BT_HDR*>(
+ osi_calloc(data32.size() + sizeof(BT_HDR))),
+ },
+ },
+ };
+
+ // Fill out the deep copy data
+ data.hs_data.rsp_data.p_rpt_data->len = static_cast<uint16_t>(data32.size());
+ std::copy(data32.begin(), data32.begin() + data32.size(),
+ reinterpret_cast<uint8_t*>((data.hs_data.rsp_data.p_rpt_data + 1)));
+
+ g_bthh_callbacks_get_report_promise = std::promise<get_report_cb_t>();
+ auto future = g_bthh_callbacks_get_report_promise.get_future();
+ bthh_callbacks.get_report_cb = [](RawAddress* bd_addr,
+ bthh_status_t hh_status, uint8_t* rpt_data,
+ int rpt_size) {
+ get_report_cb_t report = {
+ .raw_address = *bd_addr,
+ .status = hh_status,
+ .data = std::vector<uint8_t>(),
+ };
+ report.data.assign(rpt_data, rpt_data + rpt_size),
+ g_bthh_callbacks_get_report_promise.set_value(report);
+ };
+
+ bte_hh_evt(BTA_HH_GET_RPT_EVT, &data);
+ osi_free(data.hs_data.rsp_data.p_rpt_data);
+
+ ASSERT_EQ(std::future_status::ready, future.wait_for(2s));
+ auto report = future.get();
+
+ // Verify data was delivered
+ ASSERT_STREQ(kDeviceAddress.ToString().c_str(),
+ report.raw_address.ToString().c_str());
+ ASSERT_EQ(BTHH_OK, report.status);
+ int i = 0;
+ for (const auto& data : data32) {
+ ASSERT_EQ(data, report.data[i++]);
+ }
+}
diff --git a/system/btif/test/btif_rc_test.cc b/system/btif/test/btif_rc_test.cc
index 155b52e..a1ef7cc 100644
--- a/system/btif/test/btif_rc_test.cc
+++ b/system/btif/test/btif_rc_test.cc
@@ -41,6 +41,7 @@
uint8_t appl_trace_level = BT_TRACE_LEVEL_WARNING;
uint8_t btif_trace_level = BT_TRACE_LEVEL_WARNING;
+bool avrcp_absolute_volume_is_enabled() { return true; }
tAVRC_STS AVRC_BldCommand(tAVRC_COMMAND* p_cmd, BT_HDR** pp_pkt) { return 0; }
tAVRC_STS AVRC_BldResponse(uint8_t handle, tAVRC_RESPONSE* p_rsp,
BT_HDR** pp_pkt) {
diff --git a/system/device/include/interop.h b/system/device/include/interop.h
index 0f1ff22..7b8eb1a 100644
--- a/system/device/include/interop.h
+++ b/system/device/include/interop.h
@@ -118,7 +118,14 @@
INTEROP_SLC_SKIP_BIND_COMMAND,
// Respond AVRCP profile version only 1.3 for some device.
- INTEROP_AVRCP_1_3_ONLY
+ INTEROP_AVRCP_1_3_ONLY,
+
+ // Some remote devices have LMP version in[5.0, 5.2] but do not support
+ // robust
+ // caching or correctly response with an error. We disable the
+ // database hash
+ // lookup for such devices.
+ INTEROP_DISABLE_ROBUST_CACHING,
} interop_feature_t;
// Check if a given |addr| matches a known interoperability workaround as
diff --git a/system/device/include/interop_database.h b/system/device/include/interop_database.h
index d9dd282..08891e5 100644
--- a/system/device/include/interop_database.h
+++ b/system/device/include/interop_database.h
@@ -119,6 +119,8 @@
// Kenwood KMM-BT518HD - no audio when A2DP codec sample rate is changed
{{{0x00, 0x1d, 0x86, 0, 0, 0}}, 3, INTEROP_DISABLE_AVDTP_RECONFIGURE},
+ // http://b/255387998
+ {{{0x00, 0x1d, 0x86, 0, 0, 0}}, 3, INTEROP_DISABLE_ROLE_SWITCH},
// NAC FORD-2013 - Lincoln
{{{0x00, 0x26, 0xb4, 0, 0, 0}}, 3, INTEROP_DISABLE_ROLE_SWITCH},
@@ -153,9 +155,6 @@
// AirPods 2 - unacceptably loud volume
{{{0x9c, 0x64, 0x8b, 0, 0, 0}}, 3, INTEROP_DISABLE_ABSOLUTE_VOLUME},
- // Phonak AG - volume level not change
- {{{0x00, 0x0f, 0x59, 0, 0, 0}}, 3, INTEROP_DISABLE_ABSOLUTE_VOLUME},
-
// for skip name request,
// because BR/EDR address and ADV random address are the same
{{{0xd4, 0x7a, 0xe2, 0, 0, 0}}, 3, INTEROP_DISABLE_NAME_REQUEST},
@@ -193,6 +192,52 @@
// BMW Carkit
{{{0x00, 0x0a, 0x08, 0, 0, 0}}, 3, INTEROP_AVRCP_1_3_ONLY},
+
+ // Eero Wi-Fi Router
+ {{{0x08, 0x9b, 0xf1, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+ {{{0x20, 0xbe, 0xcd, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+ {{{0x30, 0x34, 0x22, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+ {{{0x3c, 0x5c, 0xf1, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+ {{{0x40, 0x47, 0x5e, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+ {{{0x50, 0x27, 0xa9, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+ {{{0x64, 0x97, 0x14, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+ {{{0x64, 0xc2, 0x69, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+ {{{0x68, 0x4a, 0x76, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+ {{{0x6c, 0xae, 0xf6, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+ {{{0x78, 0x76, 0x89, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+ {{{0x78, 0xd6, 0xd6, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+ {{{0x84, 0x70, 0xd7, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+ {{{0x98, 0xed, 0x7e, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+ {{{0x9c, 0x0b, 0x05, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+ {{{0x9c, 0x57, 0xbc, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+ {{{0x9c, 0xa5, 0x70, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+ {{{0xa0, 0x8e, 0x24, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+ {{{0xac, 0xec, 0x85, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+ {{{0xb4, 0x20, 0x46, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+ {{{0xb4, 0xb9, 0xe6, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+ {{{0xc0, 0x36, 0x53, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+ {{{0xc4, 0xf1, 0x74, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+ {{{0xc8, 0xb8, 0x2f, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+ {{{0xc8, 0xe3, 0x06, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+ {{{0xd4, 0x05, 0xde, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+ {{{0xd4, 0x3f, 0x32, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+ {{{0xec, 0x74, 0x27, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+ {{{0xf0, 0x21, 0xe0, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+ {{{0xf0, 0xb6, 0x61, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+ {{{0xfc, 0x3f, 0xa6, 0, 0, 0}}, 3, INTEROP_DISABLE_ROBUST_CACHING},
+};
+
+typedef struct {
+ RawAddress addr_start;
+ RawAddress addr_end;
+ interop_feature_t feature;
+} interop_addr_range_entry_t;
+
+static const interop_addr_range_entry_t interop_addr_range_database[] = {
+ // Phonak AG - volume level not change
+ {{{0x00, 0x0f, 0x59, 0x50, 0x00, 0x00}},
+ {{0x00, 0x0f, 0x59, 0x6f, 0xff, 0xff}},
+ INTEROP_DISABLE_ABSOLUTE_VOLUME},
};
typedef struct {
diff --git a/system/device/src/interop.cc b/system/device/src/interop.cc
index 5cde30d..bc6b691 100644
--- a/system/device/src/interop.cc
+++ b/system/device/src/interop.cc
@@ -44,6 +44,8 @@
const RawAddress* addr);
static bool interop_match_dynamic_(const interop_feature_t feature,
const RawAddress* addr);
+static bool interop_match_range_(const interop_feature_t feature,
+ const RawAddress* addr);
// Interface functions
@@ -52,7 +54,8 @@
CHECK(addr);
if (interop_match_fixed_(feature, addr) ||
- interop_match_dynamic_(feature, addr)) {
+ interop_match_dynamic_(feature, addr) ||
+ interop_match_range_(feature, addr)) {
LOG_INFO("%s() Device %s is a match for interop workaround %s.", __func__,
addr->ToString().c_str(), interop_feature_string_(feature));
return true;
@@ -138,7 +141,8 @@
CASE_RETURN_STR(INTEROP_DISABLE_SNIFF)
CASE_RETURN_STR(INTEROP_DISABLE_AVDTP_SUSPEND)
CASE_RETURN_STR(INTEROP_SLC_SKIP_BIND_COMMAND)
- CASE_RETURN_STR(INTEROP_AVRCP_1_3_ONLY);
+ CASE_RETURN_STR(INTEROP_AVRCP_1_3_ONLY)
+ CASE_RETURN_STR(INTEROP_DISABLE_ROBUST_CACHING);
}
return "UNKNOWN";
@@ -190,3 +194,20 @@
return false;
}
+
+static bool interop_match_range_(const interop_feature_t feature,
+ const RawAddress* addr) {
+ CHECK(addr);
+
+ const size_t db_size =
+ sizeof(interop_addr_range_database) / sizeof(interop_addr_range_entry_t);
+ for (size_t i = 0; i != db_size; ++i) {
+ if (feature == interop_addr_range_database[i].feature &&
+ *addr >= interop_addr_range_database[i].addr_start &&
+ *addr <= interop_addr_range_database[i].addr_end) {
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/system/device/test/interop_test.cc b/system/device/test/interop_test.cc
index 2e1598e..6c7803a 100644
--- a/system/device/test/interop_test.cc
+++ b/system/device/test/interop_test.cc
@@ -82,3 +82,26 @@
EXPECT_FALSE(interop_match_name(INTEROP_DISABLE_AUTO_PAIRING, "audi"));
EXPECT_FALSE(interop_match_name(INTEROP_AUTO_RETRY_PAIRING, "BMW M3"));
}
+
+TEST(InteropTest, test_range_hit) {
+ RawAddress test_address;
+ RawAddress::FromString("00:0f:59:50:00:00", test_address);
+ ASSERT_TRUE(
+ interop_match_addr(INTEROP_DISABLE_ABSOLUTE_VOLUME, &test_address));
+ RawAddress::FromString("00:0f:59:59:12:34", test_address);
+ ASSERT_TRUE(
+ interop_match_addr(INTEROP_DISABLE_ABSOLUTE_VOLUME, &test_address));
+ RawAddress::FromString("00:0f:59:6f:ff:ff", test_address);
+ ASSERT_TRUE(
+ interop_match_addr(INTEROP_DISABLE_ABSOLUTE_VOLUME, &test_address));
+}
+
+TEST(InteropTest, test_range_miss) {
+ RawAddress test_address;
+ RawAddress::FromString("00:0f:59:49:12:34", test_address);
+ ASSERT_FALSE(
+ interop_match_addr(INTEROP_DISABLE_ABSOLUTE_VOLUME, &test_address));
+ RawAddress::FromString("00:0f:59:70:12:34", test_address);
+ ASSERT_FALSE(
+ interop_match_addr(INTEROP_DISABLE_ABSOLUTE_VOLUME, &test_address));
+}
diff --git a/system/gd/Android.bp b/system/gd/Android.bp
index ed6c375..d2ecc50 100644
--- a/system/gd/Android.bp
+++ b/system/gd/Android.bp
@@ -394,6 +394,7 @@
":BluetoothShimTestSources",
":BluetoothSecurityUnitTestSources",
":BluetoothStorageUnitTestSources",
+ ":BluetoothBtaaSources_linux_generic_tests",
],
generated_headers: [
"BluetoothGeneratedBundlerSchema_h_bfbs",
diff --git a/system/gd/btaa/Android.bp b/system/gd/btaa/Android.bp
index 2aea7ee..6b4c269 100644
--- a/system/gd/btaa/Android.bp
+++ b/system/gd/btaa/Android.bp
@@ -30,3 +30,10 @@
"linux_generic/wakelock_processor.cc",
],
}
+
+filegroup {
+ name: "BluetoothBtaaSources_linux_generic_tests",
+ srcs: [
+ "linux_generic/attribution_processor_tests.cc",
+ ],
+}
diff --git a/system/gd/btaa/attribution_processor.h b/system/gd/btaa/attribution_processor.h
index d18a8ce..e28e6cb 100644
--- a/system/gd/btaa/attribution_processor.h
+++ b/system/gd/btaa/attribution_processor.h
@@ -82,7 +82,20 @@
void Dump(
std::promise<flatbuffers::Offset<ActivityAttributionData>> promise, flatbuffers::FlatBufferBuilder* fb_builder);
+ using ClockType = std::chrono::time_point<std::chrono::system_clock>;
+ using NowFunc = ClockType (*)();
+
+ // by default, we use the std::chrono::system_clock::now implementation to
+ // get the current timestamp
+ AttributionProcessor() : now_func_(std::chrono::system_clock::now) {}
+ // in other cases, we may need to use different implementation
+ // e.g., for testing purposes
+ AttributionProcessor(NowFunc func) : now_func_(func) {}
+
private:
+ // this function is added for testing support in
+ // OnWakelockReleased
+ NowFunc now_func_ = std::chrono::system_clock::now;
bool wakeup_ = false;
std::unordered_map<AddressActivityKey, BtaaAggregationEntry, AddressActivityKeyHasher> btaa_aggregator_;
std::unordered_map<AddressActivityKey, BtaaAggregationEntry, AddressActivityKeyHasher> wakelock_duration_aggregator_;
diff --git a/system/gd/btaa/linux_generic/attribution_processor.cc b/system/gd/btaa/linux_generic/attribution_processor.cc
index 62e62ae..eadad9d 100644
--- a/system/gd/btaa/linux_generic/attribution_processor.cc
+++ b/system/gd/btaa/linux_generic/attribution_processor.cc
@@ -69,7 +69,7 @@
return;
}
- auto cur_time = std::chrono::system_clock::now();
+ auto cur_time = now_func_();
for (auto& it : wakelock_duration_aggregator_) {
it.second.wakelock_duration_ms = (uint64_t)duration_ms * it.second.byte_count / total_byte_count;
if (btaa_aggregator_.find(it.first) == btaa_aggregator_.end()) {
@@ -126,23 +126,29 @@
}
// Trim down the transient entries in the aggregator to avoid that it overgrows
if (btaa_aggregator_.size() > kMapSizeTrimDownAggregationEntry) {
- for (auto& it : btaa_aggregator_) {
+ auto it = btaa_aggregator_.begin();
+ while (it != btaa_aggregator_.end()) {
auto elapsed_time_sec =
- std::chrono::duration_cast<std::chrono::seconds>(cur_time - it.second.creation_time).count();
+ std::chrono::duration_cast<std::chrono::seconds>(cur_time - it->second.creation_time).count();
if (elapsed_time_sec > kDurationTransientDeviceActivityEntrySecs &&
- it.second.byte_count < kByteCountTransientDeviceActivityEntry) {
- btaa_aggregator_.erase(it.first);
+ it->second.byte_count < kByteCountTransientDeviceActivityEntry) {
+ it = btaa_aggregator_.erase(it);
+ } else {
+ it++;
}
}
}
if (app_activity_aggregator_.size() > kMapSizeTrimDownAggregationEntry) {
- for (auto& it : app_activity_aggregator_) {
+ auto it = app_activity_aggregator_.begin();
+ while (it != app_activity_aggregator_.end()) {
auto elapsed_time_sec =
- std::chrono::duration_cast<std::chrono::seconds>(cur_time - it.second.creation_time).count();
+ std::chrono::duration_cast<std::chrono::seconds>(cur_time - it->second.creation_time).count();
if (elapsed_time_sec > kDurationTransientDeviceActivityEntrySecs &&
- it.second.byte_count < kByteCountTransientDeviceActivityEntry) {
- app_activity_aggregator_.erase(it.first);
+ it->second.byte_count < kByteCountTransientDeviceActivityEntry) {
+ it = app_activity_aggregator_.erase(it);
+ } else {
+ it++;
}
}
}
diff --git a/system/gd/btaa/linux_generic/attribution_processor_tests.cc b/system/gd/btaa/linux_generic/attribution_processor_tests.cc
new file mode 100644
index 0000000..1a27206
--- /dev/null
+++ b/system/gd/btaa/linux_generic/attribution_processor_tests.cc
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <base/strings/stringprintf.h>
+#include <gtest/gtest.h>
+#include <unistd.h>
+
+#include <chrono>
+#include <functional>
+#include <memory>
+#include <vector>
+
+#include "btaa/activity_attribution.h"
+#include "btaa/attribution_processor.h"
+
+using bluetooth::hci::Address;
+using namespace bluetooth::activity_attribution;
+using namespace std::chrono;
+
+// mock for std::chrono::system_clock::now
+static AttributionProcessor::ClockType now_ret_val;
+static AttributionProcessor::ClockType fake_now() {
+ return now_ret_val;
+}
+
+class AttributionProcessorTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ pAttProc = std::make_unique<AttributionProcessor>(fake_now);
+ }
+ void TearDown() override {
+ pAttProc.reset();
+ }
+
+ std::unique_ptr<AttributionProcessor> pAttProc;
+};
+
+static void fake_now_set_current() {
+ now_ret_val = system_clock::now();
+}
+
+static void fake_now_advance_1000sec() {
+ now_ret_val += seconds(1000s);
+}
+
+TEST_F(AttributionProcessorTest, UAFInOnWakelockReleasedRegressionTest) {
+ std::vector<BtaaHciPacket> btaaPackets;
+ Address addr;
+
+ fake_now_set_current();
+
+ // setup the condition 1 for triggering erase operation
+ // add 220 entries in app_activity_aggregator_
+ // and btaa_aggregator_
+ for (int i = 0; i < 220; i++) {
+ std::string addrStr = base::StringPrintf("21:43:65:87:a9:%02x", i + 10);
+ ASSERT_TRUE(Address::FromString(addrStr, addr));
+ BtaaHciPacket packet(Activity::ACL, addr, 30 * i);
+ btaaPackets.push_back(packet);
+ pAttProc->NotifyActivityAttributionInfo(i + 1000, "com.test.app" + std::to_string(i), addrStr);
+ }
+
+ pAttProc->OnBtaaPackets(btaaPackets);
+ pAttProc->OnWakelockReleased(100);
+
+ // setup the condition 2 for triggering erase operation
+ // make elapsed_time_sec > 900s
+ fake_now_advance_1000sec();
+
+ pAttProc->OnBtaaPackets(btaaPackets);
+ pAttProc->OnWakelockReleased(100);
+}
diff --git a/system/gd/common/circular_buffer_test.cc b/system/gd/common/circular_buffer_test.cc
index dc238c2..9a2ef4e 100644
--- a/system/gd/common/circular_buffer_test.cc
+++ b/system/gd/common/circular_buffer_test.cc
@@ -68,6 +68,7 @@
}
TEST(CircularBufferTest, test_timestamps) {
+ timestamp_ = 0;
bluetooth::common::TimestampedCircularBuffer<std::string> buffer(10, std::make_unique<TestTimestamper>());
buffer.Push(std::string("One"));
diff --git a/system/gd/common/init_flags.fbs b/system/gd/common/init_flags.fbs
index d1d8c12..0953465 100644
--- a/system/gd/common/init_flags.fbs
+++ b/system/gd/common/init_flags.fbs
@@ -14,8 +14,10 @@
asynchronously_start_l2cap_coc_is_enabled:bool (privacy:"Any");
btaa_hci_is_enabled:bool (privacy:"Any");
+ bta_dm_clear_conn_id_on_client_close_is_enabled:bool (privacy:"Any");
btm_dm_flush_discovery_queue_on_search_cancel_is_enabled:bool (privacy:"Any");
- finite_att_timeout_is_enabled:bool (privacy:"Any");
+ clear_hidd_interrupt_cid_on_disconnect_is_enabled:bool (privacy:"Any");
+ delay_hidh_cleanup_until_hidh_ready_start_is_enabled:bool (privacy:"Any");
gatt_robust_caching_client_is_enabled:bool (privacy:"Any");
gatt_robust_caching_server_is_enabled:bool (privacy:"Any");
gd_core_is_enabled:bool (privacy:"Any");
diff --git a/system/gd/common/interfaces/ILoggable.h b/system/gd/common/interfaces/ILoggable.h
new file mode 100644
index 0000000..ad2b8ab
--- /dev/null
+++ b/system/gd/common/interfaces/ILoggable.h
@@ -0,0 +1,45 @@
+/******************************************************************************
+ *
+ * Copyright 2022 Google, Inc.
+ *
+ * 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.
+ *
+ ******************************************************************************/
+
+#pragma once
+
+#include <string>
+
+namespace bluetooth {
+namespace common {
+
+class ILoggable {
+ public:
+ // the interface for
+ // converting an object to a string for feeding to loggers
+ // e.g.. logcat
+ virtual std::string ToStringForLogging() const = 0;
+ virtual ~ILoggable() = default;
+};
+
+class IRedactableLoggable : public ILoggable {
+ public:
+ // the interface for
+ // converting an object to a string with sensitive info redacted
+ // to avoid violating privacy
+ virtual std::string ToRedactedStringForLogging() const = 0;
+ virtual ~IRedactableLoggable() = default;
+};
+
+} // namespace common
+} // namespace bluetooth
diff --git a/system/gd/common/testing/host/log_capture_test.cc b/system/gd/common/testing/host/log_capture_test.cc
index cbd6a1c..320b4fe 100644
--- a/system/gd/common/testing/host/log_capture_test.cc
+++ b/system/gd/common/testing/host/log_capture_test.cc
@@ -62,7 +62,8 @@
ASSERT_TRUE(log_capture->Size() == 0);
}
-TEST_F(LogCaptureTest, truncate) {
+// b/260917913
+TEST_F(LogCaptureTest, DISABLED_truncate) {
std::unique_ptr<LogCapture> log_capture = std::make_unique<LogCapture>();
CalibrateOneLine(kLogError);
@@ -76,7 +77,8 @@
ASSERT_EQ(size, log_capture->Size());
}
-TEST_F(LogCaptureTest, log_size) {
+// b/260917913
+TEST_F(LogCaptureTest, DISABLED_log_size) {
std::unique_ptr<LogCapture> log_capture = std::make_unique<LogCapture>();
CalibrateOneLine(kEmptyLine);
@@ -101,7 +103,8 @@
ASSERT_TRUE(log_capture->Rewind()->Find(kLogInfo));
}
-TEST_F(LogCaptureTest, typical) {
+// b/260917913
+TEST_F(LogCaptureTest, DISABLED_typical) {
bluetooth::common::InitFlags::Load(nullptr);
std::unique_ptr<LogCapture> log_capture = std::make_unique<LogCapture>();
@@ -118,7 +121,8 @@
ASSERT_FALSE(log_capture->Rewind()->Find(kLogVerbose));
}
-TEST_F(LogCaptureTest, with_logging_debug_enabled_for_all) {
+// b/260917913
+TEST_F(LogCaptureTest, DISABLED_with_logging_debug_enabled_for_all) {
bluetooth::common::InitFlags::Load(test_flags);
std::unique_ptr<LogCapture> log_capture = std::make_unique<LogCapture>();
@@ -136,7 +140,8 @@
bluetooth::common::InitFlags::Load(nullptr);
}
-TEST_F(LogCaptureTest, wait_until_log_contains) {
+// b/260917913
+TEST_F(LogCaptureTest, DISABLED_wait_until_log_contains) {
bluetooth::common::InitFlags::Load(test_flags);
std::unique_ptr<LogCapture> log_capture = std::make_unique<LogCapture>();
diff --git a/system/gd/dumpsys/bundler/Android.bp b/system/gd/dumpsys/bundler/Android.bp
index 3754611..41f5549 100644
--- a/system/gd/dumpsys/bundler/Android.bp
+++ b/system/gd/dumpsys/bundler/Android.bp
@@ -69,9 +69,8 @@
],
}
-cc_test {
+cc_test_host {
name: "bluetooth_flatbuffer_bundler_test",
- host_supported: true,
srcs: [
":BluetoothFlatbufferBundlerTestSources",
],
diff --git a/system/gd/dumpsys/init_flags.cc b/system/gd/dumpsys/init_flags.cc
index 79e1d8d..0bf3cd0 100644
--- a/system/gd/dumpsys/init_flags.cc
+++ b/system/gd/dumpsys/init_flags.cc
@@ -35,10 +35,14 @@
builder.add_asynchronously_start_l2cap_coc_is_enabled(initFlags::asynchronously_start_l2cap_coc_is_enabled());
builder.add_btaa_hci_is_enabled(initFlags::btaa_hci_is_enabled());
+ builder.add_bta_dm_clear_conn_id_on_client_close_is_enabled(
+ initFlags::bta_dm_clear_conn_id_on_client_close_is_enabled());
builder.add_btm_dm_flush_discovery_queue_on_search_cancel_is_enabled(
initFlags::btm_dm_flush_discovery_queue_on_search_cancel_is_enabled());
- builder.add_finite_att_timeout_is_enabled(initFlags::finite_att_timeout_is_enabled());
- builder.add_gatt_robust_caching_client_is_enabled(initFlags::gatt_robust_caching_client_is_enabled());
+ builder.add_clear_hidd_interrupt_cid_on_disconnect_is_enabled(
+ initFlags::clear_hidd_interrupt_cid_on_disconnect_is_enabled());
+ builder.add_delay_hidh_cleanup_until_hidh_ready_start_is_enabled(
+ initFlags::delay_hidh_cleanup_until_hidh_ready_start_is_enabled());
builder.add_gatt_robust_caching_server_is_enabled(initFlags::gatt_robust_caching_server_is_enabled());
builder.add_gd_core_is_enabled(initFlags::gd_core_is_enabled());
builder.add_gd_l2cap_is_enabled(initFlags::gd_l2cap_is_enabled());
diff --git a/system/gd/hal/snoop_logger.cc b/system/gd/hal/snoop_logger.cc
index 1b03033..e3b585c 100644
--- a/system/gd/hal/snoop_logger.cc
+++ b/system/gd/hal/snoop_logger.cc
@@ -193,6 +193,7 @@
} // namespace
const std::string SnoopLogger::kBtSnoopLogModeDisabled = "disabled";
+const std::string SnoopLogger::kBtSnoopLogModeTruncated = "truncated";
const std::string SnoopLogger::kBtSnoopLogModeFiltered = "filtered";
const std::string SnoopLogger::kBtSnoopLogModeFull = "full";
const std::string SnoopLogger::kSoCManufacturerQualcomm = "Qualcomm";
@@ -203,6 +204,10 @@
const std::string SnoopLogger::kBtSnoopDefaultLogModeProperty = "persist.bluetooth.btsnoopdefaultmode";
const std::string SnoopLogger::kSoCManufacturerProperty = "ro.soc.manufacturer";
+// The max ACL packet size (in bytes) in truncated logging mode. All information
+// past this point is truncated from a packet.
+static constexpr uint32_t kMaxTruncatedAclPacketSize = 100;
+
SnoopLogger::SnoopLogger(
std::string snoop_log_path,
std::string snooz_log_path,
@@ -236,6 +241,15 @@
delete_btsnoop_files(get_btsnoop_log_path(snoop_log_path_, true));
// delete snooz logs
delete_btsnoop_files(snooz_log_path_);
+ } else if (btsnoop_mode == kBtSnoopLogModeTruncated) {
+ LOG_INFO("Snoop Logs truncated. Limiting to %u", kMaxTruncatedAclPacketSize);
+ is_enabled_ = true;
+ is_truncated_ = true;
+ is_filtered_ = false;
+ // delete filtered logs
+ delete_btsnoop_files(get_btsnoop_log_path(snoop_log_path_, true));
+ // delete snooz logs
+ delete_btsnoop_files(snooz_log_path_);
} else {
LOG_INFO("Snoop Logs disabled");
is_enabled_ = false;
@@ -320,6 +334,9 @@
.dropped_packets = 0,
.timestamp = htonll(timestamp_us + kBtSnoopEpochDelta),
.type = static_cast<uint8_t>(type)};
+ if (is_truncated_ && type == PacketType::ACL) {
+ header.length_captured = htonl(std::min(length, kMaxTruncatedAclPacketSize));
+ }
{
std::lock_guard<std::recursive_mutex> lock(file_mutex_);
if (!is_enabled_) {
@@ -445,9 +462,8 @@
size_t SnoopLogger::GetMaxPacketsPerBuffer() {
// We want to use at most 256 KB memory for btsnooz log for release builds
// and 512 KB memory for userdebug/eng builds
- auto is_debuggable = os::GetSystemProperty(kIsDebuggableProperty);
- size_t btsnooz_max_memory_usage_bytes =
- ((is_debuggable.has_value() && common::StringTrim(is_debuggable.value()) == "1") ? 1024 : 256) * 1024;
+ auto is_debuggable = os::GetSystemPropertyBool(kIsDebuggableProperty, false);
+ size_t btsnooz_max_memory_usage_bytes = (is_debuggable ? 1024 : 256) * 1024;
// Calculate max number of packets based on max memory usage and max packet size
return btsnooz_max_memory_usage_bytes / kDefaultBtSnoozMaxBytesPerPacket;
}
@@ -457,8 +473,8 @@
// In userdebug/eng build, it can also be overwritten by modifying the global setting
std::string default_mode = kBtSnoopLogModeDisabled;
{
- auto is_debuggable = os::GetSystemProperty(kIsDebuggableProperty);
- if (is_debuggable.has_value() && common::StringTrim(is_debuggable.value()) == "1") {
+ auto is_debuggable = os::GetSystemPropertyBool(kIsDebuggableProperty, false);
+ if (is_debuggable) {
auto default_mode_property = os::GetSystemProperty(kBtSnoopDefaultLogModeProperty);
if (default_mode_property) {
default_mode = std::move(default_mode_property.value());
diff --git a/system/gd/hal/snoop_logger.h b/system/gd/hal/snoop_logger.h
index 7fa65aa..f879798 100644
--- a/system/gd/hal/snoop_logger.h
+++ b/system/gd/hal/snoop_logger.h
@@ -38,6 +38,7 @@
static const ModuleFactory Factory;
static const std::string kBtSnoopLogModeDisabled;
+ static const std::string kBtSnoopLogModeTruncated;
static const std::string kBtSnoopLogModeFiltered;
static const std::string kBtSnoopLogModeFull;
static const std::string kSoCManufacturerQualcomm;
@@ -124,6 +125,7 @@
std::ofstream btsnoop_ostream_;
bool is_enabled_ = false;
bool is_filtered_ = false;
+ bool is_truncated_ = false;
size_t max_packets_per_file_;
common::CircularBuffer<std::string> btsnooz_buffer_;
bool qualcomm_debug_log_enabled_ = false;
diff --git a/system/gd/hci/Android.bp b/system/gd/hci/Android.bp
index ae026fc..3dad0d6 100644
--- a/system/gd/hci/Android.bp
+++ b/system/gd/hci/Android.bp
@@ -33,32 +33,25 @@
filegroup {
name: "BluetoothHciUnitTestSources",
srcs: [
- "acl_manager/le_impl_test.cc",
"acl_builder_test.cc",
+ "acl_manager_test.cc",
"acl_manager_unittest.cc",
+ "acl_manager/classic_acl_connection_test.cc",
+ "acl_manager/le_impl_test.cc",
+ "acl_manager/round_robin_scheduler_test.cc",
"address_unittest.cc",
"address_with_type_test.cc",
"class_of_device_unittest.cc",
"controller_test.cc",
"hci_layer_fake.cc",
+ "hci_layer_test.cc",
"hci_layer_unittest.cc",
"hci_packets_test.cc",
"uuid_unittest.cc",
"le_periodic_sync_manager_test.cc",
"le_scanning_manager_test.cc",
"le_advertising_manager_test.cc",
- ],
-}
-
-filegroup {
- name: "BluetoothHciTestSources",
- srcs: [
- "acl_manager/round_robin_scheduler_test.cc",
- "acl_manager_test.cc",
- "hci_layer_test.cc",
"le_address_manager_test.cc",
- "le_advertising_manager_test.cc",
- "le_scanning_manager_test.cc",
],
}
diff --git a/system/gd/hci/acl_manager/classic_acl_connection_test.cc b/system/gd/hci/acl_manager/classic_acl_connection_test.cc
new file mode 100644
index 0000000..9ba38c9
--- /dev/null
+++ b/system/gd/hci/acl_manager/classic_acl_connection_test.cc
@@ -0,0 +1,340 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "hci/acl_manager/classic_acl_connection.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <chrono>
+#include <cstdint>
+#include <future>
+#include <list>
+#include <memory>
+#include <mutex>
+#include <queue>
+#include <vector>
+
+#include "hci/acl_connection_interface.h"
+#include "hci/acl_manager/connection_management_callbacks.h"
+#include "hci/address.h"
+#include "hci/hci_packets.h"
+#include "os/handler.h"
+#include "os/log.h"
+#include "os/thread.h"
+
+using namespace bluetooth;
+using namespace std::chrono_literals;
+
+namespace {
+constexpr char kAddress[] = "00:11:22:33:44:55";
+constexpr uint16_t kConnectionHandle = 123;
+constexpr size_t kQueueSize = 10;
+
+std::vector<hci::DisconnectReason> disconnect_reason_vector = {
+ hci::DisconnectReason::AUTHENTICATION_FAILURE,
+ hci::DisconnectReason::REMOTE_USER_TERMINATED_CONNECTION,
+ hci::DisconnectReason::REMOTE_DEVICE_TERMINATED_CONNECTION_LOW_RESOURCES,
+ hci::DisconnectReason::REMOTE_DEVICE_TERMINATED_CONNECTION_POWER_OFF,
+ hci::DisconnectReason::UNSUPPORTED_REMOTE_FEATURE,
+ hci::DisconnectReason::PAIRING_WITH_UNIT_KEY_NOT_SUPPORTED,
+ hci::DisconnectReason::UNACCEPTABLE_CONNECTION_PARAMETERS,
+};
+
+std::vector<hci::ErrorCode> error_code_vector = {
+ hci::ErrorCode::SUCCESS,
+ hci::ErrorCode::UNKNOWN_HCI_COMMAND,
+ hci::ErrorCode::UNKNOWN_CONNECTION,
+ hci::ErrorCode::HARDWARE_FAILURE,
+ hci::ErrorCode::PAGE_TIMEOUT,
+ hci::ErrorCode::AUTHENTICATION_FAILURE,
+ hci::ErrorCode::PIN_OR_KEY_MISSING,
+ hci::ErrorCode::MEMORY_CAPACITY_EXCEEDED,
+ hci::ErrorCode::CONNECTION_TIMEOUT,
+ hci::ErrorCode::CONNECTION_LIMIT_EXCEEDED,
+ hci::ErrorCode::SYNCHRONOUS_CONNECTION_LIMIT_EXCEEDED,
+ hci::ErrorCode::CONNECTION_ALREADY_EXISTS,
+ hci::ErrorCode::COMMAND_DISALLOWED,
+ hci::ErrorCode::CONNECTION_REJECTED_LIMITED_RESOURCES,
+ hci::ErrorCode::CONNECTION_REJECTED_SECURITY_REASONS,
+ hci::ErrorCode::CONNECTION_REJECTED_UNACCEPTABLE_BD_ADDR,
+ hci::ErrorCode::CONNECTION_ACCEPT_TIMEOUT,
+ hci::ErrorCode::UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE,
+ hci::ErrorCode::INVALID_HCI_COMMAND_PARAMETERS,
+ hci::ErrorCode::REMOTE_USER_TERMINATED_CONNECTION,
+ hci::ErrorCode::REMOTE_DEVICE_TERMINATED_CONNECTION_LOW_RESOURCES,
+ hci::ErrorCode::REMOTE_DEVICE_TERMINATED_CONNECTION_POWER_OFF,
+ hci::ErrorCode::CONNECTION_TERMINATED_BY_LOCAL_HOST,
+ hci::ErrorCode::REPEATED_ATTEMPTS,
+ hci::ErrorCode::PAIRING_NOT_ALLOWED,
+ hci::ErrorCode::UNKNOWN_LMP_PDU,
+ hci::ErrorCode::UNSUPPORTED_REMOTE_OR_LMP_FEATURE,
+ hci::ErrorCode::SCO_OFFSET_REJECTED,
+ hci::ErrorCode::SCO_INTERVAL_REJECTED,
+ hci::ErrorCode::SCO_AIR_MODE_REJECTED,
+ hci::ErrorCode::INVALID_LMP_OR_LL_PARAMETERS,
+ hci::ErrorCode::UNSPECIFIED_ERROR,
+ hci::ErrorCode::UNSUPPORTED_LMP_OR_LL_PARAMETER,
+ hci::ErrorCode::ROLE_CHANGE_NOT_ALLOWED,
+ hci::ErrorCode::TRANSACTION_RESPONSE_TIMEOUT,
+ hci::ErrorCode::LINK_LAYER_COLLISION,
+ hci::ErrorCode::ENCRYPTION_MODE_NOT_ACCEPTABLE,
+ hci::ErrorCode::ROLE_SWITCH_FAILED,
+ hci::ErrorCode::CONTROLLER_BUSY,
+ hci::ErrorCode::ADVERTISING_TIMEOUT,
+ hci::ErrorCode::CONNECTION_FAILED_ESTABLISHMENT,
+ hci::ErrorCode::LIMIT_REACHED,
+ hci::ErrorCode::STATUS_UNKNOWN,
+};
+
+// Generic template for all commands
+template <typename T, typename U>
+T CreateCommand(U u) {
+ T command;
+ return command;
+}
+
+template <>
+hci::DisconnectView CreateCommand(std::shared_ptr<std::vector<uint8_t>> bytes) {
+ return hci::DisconnectView::Create(
+ hci::AclCommandView::Create(hci::CommandView::Create(hci::PacketView<hci::kLittleEndian>(bytes))));
+}
+
+} // namespace
+
+class TestAclConnectionInterface : public hci::AclConnectionInterface {
+ private:
+ void EnqueueCommand(
+ std::unique_ptr<hci::AclCommandBuilder> command,
+ common::ContextualOnceCallback<void(hci::CommandStatusView)> on_status) override {
+ const std::lock_guard<std::mutex> lock(command_queue_mutex_);
+ command_queue_.push(std::move(command));
+ command_status_callbacks.push_back(std::move(on_status));
+ if (command_promise_ != nullptr) {
+ std::promise<void>* prom = command_promise_.release();
+ prom->set_value();
+ delete prom;
+ }
+ }
+
+ void EnqueueCommand(
+ std::unique_ptr<hci::AclCommandBuilder> command,
+ common::ContextualOnceCallback<void(hci::CommandCompleteView)> on_complete) override {
+ const std::lock_guard<std::mutex> lock(command_queue_mutex_);
+ command_queue_.push(std::move(command));
+ command_complete_callbacks.push_back(std::move(on_complete));
+ if (command_promise_ != nullptr) {
+ std::promise<void>* prom = command_promise_.release();
+ prom->set_value();
+ delete prom;
+ }
+ }
+
+ public:
+ virtual ~TestAclConnectionInterface() = default;
+
+ std::unique_ptr<hci::CommandBuilder> DequeueCommand() {
+ const std::lock_guard<std::mutex> lock(command_queue_mutex_);
+ auto packet = std::move(command_queue_.front());
+ command_queue_.pop();
+ return std::move(packet);
+ }
+
+ std::shared_ptr<std::vector<uint8_t>> DequeueCommandBytes() {
+ auto command = DequeueCommand();
+ auto bytes = std::make_shared<std::vector<uint8_t>>();
+ packet::BitInserter bi(*bytes);
+ command->Serialize(bi);
+ return bytes;
+ }
+
+ bool IsPacketQueueEmpty() const {
+ const std::lock_guard<std::mutex> lock(command_queue_mutex_);
+ return command_queue_.empty();
+ }
+
+ size_t NumberOfQueuedCommands() const {
+ const std::lock_guard<std::mutex> lock(command_queue_mutex_);
+ return command_queue_.size();
+ }
+
+ private:
+ std::list<common::ContextualOnceCallback<void(hci::CommandCompleteView)>> command_complete_callbacks;
+ std::list<common::ContextualOnceCallback<void(hci::CommandStatusView)>> command_status_callbacks;
+ std::queue<std::unique_ptr<hci::CommandBuilder>> command_queue_;
+ mutable std::mutex command_queue_mutex_;
+ std::unique_ptr<std::promise<void>> command_promise_;
+ std::unique_ptr<std::future<void>> command_future_;
+};
+
+class TestConnectionManagementCallbacks : public hci::acl_manager::ConnectionManagementCallbacks {
+ public:
+ ~TestConnectionManagementCallbacks() = default;
+ void OnConnectionPacketTypeChanged(uint16_t packet_type) override {}
+ void OnAuthenticationComplete(hci::ErrorCode hci_status) override {}
+ void OnEncryptionChange(hci::EncryptionEnabled enabled) override {}
+ void OnChangeConnectionLinkKeyComplete() override {}
+ void OnReadClockOffsetComplete(uint16_t clock_offset) override {}
+ void OnModeChange(hci::ErrorCode status, hci::Mode current_mode, uint16_t interval) override {}
+ void OnSniffSubrating(
+ hci::ErrorCode hci_status,
+ uint16_t maximum_transmit_latency,
+ uint16_t maximum_receive_latency,
+ uint16_t minimum_remote_timeout,
+ uint16_t minimum_local_timeout) override {}
+ void OnQosSetupComplete(
+ hci::ServiceType service_type,
+ uint32_t token_rate,
+ uint32_t peak_bandwidth,
+ uint32_t latency,
+ uint32_t delay_variation) override {}
+ void OnFlowSpecificationComplete(
+ hci::FlowDirection flow_direction,
+ hci::ServiceType service_type,
+ uint32_t token_rate,
+ uint32_t token_bucket_size,
+ uint32_t peak_bandwidth,
+ uint32_t access_latency) override {}
+ void OnFlushOccurred() override {}
+ void OnRoleDiscoveryComplete(hci::Role current_role) override {}
+ void OnReadLinkPolicySettingsComplete(uint16_t link_policy_settings) override {}
+ void OnReadAutomaticFlushTimeoutComplete(uint16_t flush_timeout) override {}
+ void OnReadTransmitPowerLevelComplete(uint8_t transmit_power_level) override {}
+ void OnReadLinkSupervisionTimeoutComplete(uint16_t link_supervision_timeout) override {}
+ void OnReadFailedContactCounterComplete(uint16_t failed_contact_counter) override {}
+ void OnReadLinkQualityComplete(uint8_t link_quality) override {}
+ void OnReadAfhChannelMapComplete(hci::AfhMode afh_mode, std::array<uint8_t, 10> afh_channel_map) override {}
+ void OnReadRssiComplete(uint8_t rssi) override {}
+ void OnReadClockComplete(uint32_t clock, uint16_t accuracy) override {}
+ void OnCentralLinkKeyComplete(hci::KeyFlag key_flag) override {}
+ void OnRoleChange(hci::ErrorCode hci_status, hci::Role new_role) override {}
+ void OnDisconnection(hci::ErrorCode reason) override {
+ on_disconnection_error_code_queue_.push(reason);
+ }
+ void OnReadRemoteVersionInformationComplete(
+ hci::ErrorCode hci_status, uint8_t lmp_version, uint16_t manufacturer_name, uint16_t sub_version) override {}
+ void OnReadRemoteSupportedFeaturesComplete(uint64_t features) override {}
+ void OnReadRemoteExtendedFeaturesComplete(uint8_t page_number, uint8_t max_page_number, uint64_t features) override {}
+
+ std::queue<hci::ErrorCode> on_disconnection_error_code_queue_;
+};
+
+namespace bluetooth {
+namespace hci {
+namespace acl_manager {
+
+class ClassicAclConnectionTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ ASSERT_TRUE(hci::Address::FromString(kAddress, address_));
+ thread_ = new os::Thread("thread", os::Thread::Priority::NORMAL);
+ handler_ = new os::Handler(thread_);
+ queue_ = std::make_shared<hci::acl_manager::AclConnection::Queue>(kQueueSize);
+ sync_handler();
+ }
+
+ void TearDown() override {
+ handler_->Clear();
+ delete handler_;
+ delete thread_;
+ }
+
+ void sync_handler() {
+ ASSERT(handler_ != nullptr);
+
+ auto promise = std::promise<void>();
+ auto future = promise.get_future();
+ handler_->BindOnceOn(&promise, &std::promise<void>::set_value).Invoke();
+ auto status = future.wait_for(2s);
+ ASSERT_EQ(status, std::future_status::ready);
+ }
+
+ Address address_;
+ os::Handler* handler_{nullptr};
+ os::Thread* thread_{nullptr};
+ std::shared_ptr<hci::acl_manager::AclConnection::Queue> queue_;
+
+ TestAclConnectionInterface acl_connection_interface_;
+ TestConnectionManagementCallbacks callbacks_;
+};
+
+TEST_F(ClassicAclConnectionTest, simple) {
+ AclConnectionInterface* acl_connection_interface = nullptr;
+ ClassicAclConnection* connection =
+ new ClassicAclConnection(queue_, acl_connection_interface, kConnectionHandle, address_);
+ connection->RegisterCallbacks(&callbacks_, handler_);
+
+ delete connection;
+}
+
+class ClassicAclConnectionWithCallbacksTest : public ClassicAclConnectionTest {
+ protected:
+ void SetUp() override {
+ ClassicAclConnectionTest::SetUp();
+ connection_ =
+ std::make_unique<ClassicAclConnection>(queue_, &acl_connection_interface_, kConnectionHandle, address_);
+ connection_->RegisterCallbacks(&callbacks_, handler_);
+ is_callbacks_registered_ = true;
+ connection_management_callbacks_ =
+ connection_->GetEventCallbacks([this](uint16_t hci_handle) { is_callbacks_invalidated_ = true; });
+ is_callbacks_invalidated_ = false;
+ }
+
+ void TearDown() override {
+ connection_.reset();
+ ASSERT_TRUE(is_callbacks_invalidated_);
+ ClassicAclConnectionTest::TearDown();
+ }
+
+ protected:
+ std::unique_ptr<ClassicAclConnection> connection_;
+ ConnectionManagementCallbacks* connection_management_callbacks_;
+ bool is_callbacks_registered_{false};
+ bool is_callbacks_invalidated_{false};
+};
+
+TEST_F(ClassicAclConnectionWithCallbacksTest, Disconnect) {
+ for (const auto& reason : disconnect_reason_vector) {
+ ASSERT_TRUE(connection_->Disconnect(reason));
+ }
+
+ for (const auto& reason : disconnect_reason_vector) {
+ ASSERT_FALSE(acl_connection_interface_.IsPacketQueueEmpty());
+ auto command = CreateCommand<DisconnectView>(acl_connection_interface_.DequeueCommandBytes());
+ ASSERT_TRUE(command.IsValid());
+ ASSERT_EQ(reason, command.GetReason());
+ ASSERT_EQ(kConnectionHandle, command.GetConnectionHandle());
+ }
+ ASSERT_TRUE(acl_connection_interface_.IsPacketQueueEmpty());
+}
+
+TEST_F(ClassicAclConnectionWithCallbacksTest, OnDisconnection) {
+ for (const auto& error_code : error_code_vector) {
+ connection_management_callbacks_->OnDisconnection(error_code);
+ }
+
+ sync_handler();
+ ASSERT_TRUE(!callbacks_.on_disconnection_error_code_queue_.empty());
+
+ for (const auto& error_code : error_code_vector) {
+ ASSERT_EQ(error_code, callbacks_.on_disconnection_error_code_queue_.front());
+ callbacks_.on_disconnection_error_code_queue_.pop();
+ }
+}
+
+} // namespace acl_manager
+} // namespace hci
+} // namespace bluetooth
diff --git a/system/gd/hci/acl_manager/le_impl_test.cc b/system/gd/hci/acl_manager/le_impl_test.cc
index ca9d022..d84f95b 100644
--- a/system/gd/hci/acl_manager/le_impl_test.cc
+++ b/system/gd/hci/acl_manager/le_impl_test.cc
@@ -62,12 +62,14 @@
[[maybe_unused]] constexpr bool kSkipFilterAcceptList = !kAddToFilterAcceptList;
[[maybe_unused]] constexpr bool kIsDirectConnection = true;
[[maybe_unused]] constexpr bool kIsBackgroundConnection = !kIsDirectConnection;
-constexpr ::bluetooth::crypto_toolbox::Octet16 kRotationIrk = {};
+constexpr crypto_toolbox::Octet16 kRotationIrk = {};
constexpr std::chrono::milliseconds kMinimumRotationTime(14 * 1000);
constexpr std::chrono::milliseconds kMaximumRotationTime(16 * 1000);
-constexpr uint16_t kIntervalMin = 0x20;
constexpr uint16_t kIntervalMax = 0x40;
+constexpr uint16_t kIntervalMin = 0x20;
constexpr uint16_t kLatency = 0x60;
+constexpr uint16_t kLength = 0x5678;
+constexpr uint16_t kTime = 0x1234;
constexpr uint16_t kTimeout = 0x80;
constexpr std::array<uint8_t, 16> kPeerIdentityResolvingKey({
0x00,
@@ -106,11 +108,12 @@
0x8f,
});
-// Generic template for all commands
-template <typename T, typename U>
-T CreateCommand(U u) {
- T command;
- return command;
+template <typename B>
+std::shared_ptr<std::vector<uint8_t>> Serialize(std::unique_ptr<B> build) {
+ auto bytes = std::make_shared<std::vector<uint8_t>>();
+ BitInserter bi(*bytes);
+ build->Serialize(bi);
+ return bytes;
}
template <typename T>
@@ -140,20 +143,16 @@
[[maybe_unused]] hci::CommandCompleteView ReturnCommandComplete(hci::OpCode op_code, hci::ErrorCode error_code) {
std::vector<uint8_t> success_vector{static_cast<uint8_t>(error_code)};
- auto bytes = std::make_shared<std::vector<uint8_t>>();
- BitInserter bi(*bytes);
auto builder = hci::CommandCompleteBuilder::Create(uint8_t{1}, op_code, std::make_unique<RawBuilder>(success_vector));
- builder->Serialize(bi);
+ auto bytes = Serialize<hci::CommandCompleteBuilder>(std::move(builder));
return hci::CommandCompleteView::Create(hci::EventView::Create(hci::PacketView<hci::kLittleEndian>(bytes)));
}
[[maybe_unused]] hci::CommandStatusView ReturnCommandStatus(hci::OpCode op_code, hci::ErrorCode error_code) {
std::vector<uint8_t> success_vector{static_cast<uint8_t>(error_code)};
- auto bytes = std::make_shared<std::vector<uint8_t>>();
- BitInserter bi(*bytes);
auto builder = hci::CommandStatusBuilder::Create(
hci::ErrorCode::SUCCESS, uint8_t{1}, op_code, std::make_unique<RawBuilder>(success_vector));
- builder->Serialize(bi);
+ auto bytes = Serialize<hci::CommandStatusBuilder>(std::move(builder));
return hci::CommandStatusView::Create(hci::EventView::Create(hci::PacketView<hci::kLittleEndian>(bytes)));
}
@@ -256,8 +255,9 @@
command_queue_.push(std::move(command));
command_status_callbacks.push_back(std::move(on_status));
if (command_promise_ != nullptr) {
- command_promise_->set_value();
- command_promise_.reset();
+ std::promise<void>* prom = command_promise_.release();
+ prom->set_value();
+ delete prom;
}
}
@@ -268,8 +268,9 @@
command_queue_.push(std::move(command));
command_complete_callbacks.push_back(std::move(on_complete));
if (command_promise_ != nullptr) {
- command_promise_->set_value();
- command_promise_.reset();
+ std::promise<void>* prom = command_promise_.release();
+ prom->set_value();
+ delete prom;
}
}
@@ -300,7 +301,7 @@
}
void SetCommandFuture() {
- ASSERT_LOG(command_promise_ == nullptr, "Promises, Promises, ... Only one at a time.");
+ ASSERT_EQ(command_promise_, nullptr) << "Promises, Promises, ... Only one at a time.";
command_promise_ = std::make_unique<std::promise<void>>();
command_future_ = std::make_unique<std::future<void>>(command_promise_->get_future());
}
@@ -425,6 +426,7 @@
class LeImplTest : public ::testing::Test {
protected:
void SetUp() override {
+ bluetooth::common::InitFlags::SetAllForTesting();
thread_ = new Thread("thread", Thread::Priority::NORMAL);
handler_ = new Handler(thread_);
controller_ = new TestController();
@@ -449,7 +451,7 @@
void set_random_device_address_policy() {
// Set address policy
- hci_layer_->SetCommandFuture();
+ ASSERT_NO_FATAL_FAILURE(hci_layer_->SetCommandFuture());
hci::Address address;
Address::FromString("D0:05:04:03:02:01", address);
hci::AddressWithType address_with_type(address, hci::AddressType::RANDOM_DEVICE_ADDRESS);
@@ -571,6 +573,7 @@
protected:
void SetUp() override {
LeImplTest::SetUp();
+ set_random_device_address_policy();
EXPECT_CALL(mock_le_connection_callbacks_, OnLeConnectSuccess(_, _))
.WillOnce([&](AddressWithType addr, std::unique_ptr<LeAclConnection> conn) {
@@ -579,8 +582,6 @@
connection_->RegisterCallbacks(&connection_management_callbacks_, handler_);
});
- auto bytes = std::make_shared<std::vector<uint8_t>>();
- BitInserter bi(*bytes);
auto command = LeEnhancedConnectionCompleteBuilder::Create(
ErrorCode::SUCCESS,
kHciHandle,
@@ -593,7 +594,7 @@
0x0000,
0x0011,
ClockAccuracy::PPM_30);
- command->Serialize(bi);
+ auto bytes = Serialize<LeEnhancedConnectionCompleteBuilder>(std::move(command));
auto view = CreateLeEventView<hci::LeEnhancedConnectionCompleteView>(bytes);
ASSERT_TRUE(view.IsValid());
le_impl_->on_le_event(view);
@@ -603,6 +604,7 @@
}
void TearDown() override {
+ connection_.reset();
LeImplTest::TearDown();
}
@@ -652,11 +654,11 @@
set_random_device_address_policy();
// Create connection
- hci_layer_->SetCommandFuture();
+ ASSERT_NO_FATAL_FAILURE(hci_layer_->SetCommandFuture());
le_impl_->create_le_connection(
{{0x21, 0x22, 0x23, 0x24, 0x25, 0x26}, AddressType::PUBLIC_DEVICE_ADDRESS}, true, false);
hci_layer_->GetCommand(OpCode::LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST);
- hci_layer_->SetCommandFuture();
+ ASSERT_NO_FATAL_FAILURE(hci_layer_->SetCommandFuture());
hci_layer_->CommandCompleteCallback(LeAddDeviceToFilterAcceptListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
hci_layer_->GetCommand(OpCode::LE_CREATE_CONNECTION);
hci_layer_->CommandStatusCallback(LeCreateConnectionStatusBuilder::Create(ErrorCode::SUCCESS, 0x01));
@@ -669,7 +671,7 @@
hci::Address remote_address;
Address::FromString("D0:05:04:03:02:01", remote_address);
hci::AddressWithType address_with_type(remote_address, hci::AddressType::PUBLIC_DEVICE_ADDRESS);
- EXPECT_CALL(mock_le_connection_callbacks_, OnLeConnectSuccess(address_with_type, ::testing::_));
+ EXPECT_CALL(mock_le_connection_callbacks_, OnLeConnectSuccess(address_with_type, _));
hci_layer_->IncomingLeMetaEvent(LeConnectionCompleteBuilder::Create(
ErrorCode::SUCCESS,
0x0041,
@@ -691,11 +693,11 @@
controller_->AddSupported(OpCode::LE_EXTENDED_CREATE_CONNECTION);
// Create connection
- hci_layer_->SetCommandFuture();
+ ASSERT_NO_FATAL_FAILURE(hci_layer_->SetCommandFuture());
le_impl_->create_le_connection(
{{0x21, 0x22, 0x23, 0x24, 0x25, 0x26}, AddressType::PUBLIC_DEVICE_ADDRESS}, true, false);
hci_layer_->GetCommand(OpCode::LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST);
- hci_layer_->SetCommandFuture();
+ ASSERT_NO_FATAL_FAILURE(hci_layer_->SetCommandFuture());
hci_layer_->CommandCompleteCallback(LeAddDeviceToFilterAcceptListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
hci_layer_->GetCommand(OpCode::LE_EXTENDED_CREATE_CONNECTION);
hci_layer_->CommandStatusCallback(LeExtendedCreateConnectionStatusBuilder::Create(ErrorCode::SUCCESS, 0x01));
@@ -708,7 +710,7 @@
hci::Address remote_address;
Address::FromString("D0:05:04:03:02:01", remote_address);
hci::AddressWithType address_with_type(remote_address, hci::AddressType::PUBLIC_DEVICE_ADDRESS);
- EXPECT_CALL(mock_le_connection_callbacks_, OnLeConnectSuccess(address_with_type, ::testing::_));
+ EXPECT_CALL(mock_le_connection_callbacks_, OnLeConnectSuccess(address_with_type, _));
hci_layer_->IncomingLeMetaEvent(LeEnhancedConnectionCompleteBuilder::Create(
ErrorCode::SUCCESS,
0x0041,
@@ -734,10 +736,10 @@
Address::FromString("D0:05:04:03:02:01", remote_address);
hci::AddressWithType address_with_type(remote_address, hci::AddressType::PUBLIC_DEVICE_ADDRESS);
// Create connection
- hci_layer_->SetCommandFuture();
+ ASSERT_NO_FATAL_FAILURE(hci_layer_->SetCommandFuture());
le_impl_->create_le_connection(address_with_type, true, false);
hci_layer_->GetCommand(OpCode::LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST);
- hci_layer_->SetCommandFuture();
+ ASSERT_NO_FATAL_FAILURE(hci_layer_->SetCommandFuture());
hci_layer_->CommandCompleteCallback(LeAddDeviceToFilterAcceptListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
hci_layer_->GetCommand(OpCode::LE_CREATE_CONNECTION);
hci_layer_->CommandStatusCallback(LeCreateConnectionStatusBuilder::Create(ErrorCode::SUCCESS, 0x01));
@@ -747,7 +749,7 @@
ASSERT_EQ(ConnectabilityState::ARMED, le_impl_->connectability_state_);
// Receive connection complete of outgoing connection (Role::CENTRAL)
- EXPECT_CALL(mock_le_connection_callbacks_, OnLeConnectSuccess(address_with_type, ::testing::_));
+ EXPECT_CALL(mock_le_connection_callbacks_, OnLeConnectSuccess(address_with_type, _));
hci_layer_->IncomingLeMetaEvent(LeConnectionCompleteBuilder::Create(
ErrorCode::SUCCESS,
0x0041,
@@ -772,10 +774,10 @@
Address::FromString("D0:05:04:03:02:01", remote_address);
hci::AddressWithType address_with_type(remote_address, hci::AddressType::PUBLIC_DEVICE_ADDRESS);
// Create connection
- hci_layer_->SetCommandFuture();
+ ASSERT_NO_FATAL_FAILURE(hci_layer_->SetCommandFuture());
le_impl_->create_le_connection(address_with_type, true, false);
hci_layer_->GetCommand(OpCode::LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST);
- hci_layer_->SetCommandFuture();
+ ASSERT_NO_FATAL_FAILURE(hci_layer_->SetCommandFuture());
hci_layer_->CommandCompleteCallback(LeAddDeviceToFilterAcceptListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
hci_layer_->GetCommand(OpCode::LE_EXTENDED_CREATE_CONNECTION);
hci_layer_->CommandStatusCallback(LeExtendedCreateConnectionStatusBuilder::Create(ErrorCode::SUCCESS, 0x01));
@@ -785,7 +787,7 @@
ASSERT_EQ(ConnectabilityState::ARMED, le_impl_->connectability_state_);
// Receive connection complete of outgoing connection (Role::CENTRAL)
- EXPECT_CALL(mock_le_connection_callbacks_, OnLeConnectSuccess(address_with_type, ::testing::_));
+ EXPECT_CALL(mock_le_connection_callbacks_, OnLeConnectSuccess(address_with_type, _));
hci_layer_->IncomingLeMetaEvent(LeEnhancedConnectionCompleteBuilder::Create(
ErrorCode::SUCCESS,
0x0041,
@@ -804,8 +806,8 @@
ASSERT_EQ(ConnectabilityState::DISARMED, le_impl_->connectability_state_);
}
-TEST_F(LeImplTest, register_with_address_manager__AddressPolicyNotSet) {
- bluetooth::common::InitFlags::SetAllForTesting();
+// b/260917913
+TEST_F(LeImplTest, DISABLED_register_with_address_manager__AddressPolicyNotSet) {
auto log_capture = std::make_unique<LogCapture>();
std::promise<void> promise;
@@ -857,8 +859,8 @@
std::move(log_capture)));
}
-TEST_F(LeImplTest, disarm_connectability_DISARMED) {
- bluetooth::common::InitFlags::SetAllForTesting();
+// b/260917913
+TEST_F(LeImplTest, DISABLED_disarm_connectability_DISARMED) {
std::unique_ptr<LogCapture> log_capture = std::make_unique<LogCapture>();
le_impl_->connectability_state_ = ConnectabilityState::DISARMED;
@@ -871,8 +873,8 @@
ASSERT_TRUE(log_capture->Rewind()->Find("in unexpected state:ConnectabilityState::DISARMED"));
}
-TEST_F(LeImplTest, disarm_connectability_DISARMED_extended) {
- bluetooth::common::InitFlags::SetAllForTesting();
+// b/260917913
+TEST_F(LeImplTest, DISABLED_disarm_connectability_DISARMED_extended) {
std::unique_ptr<LogCapture> log_capture = std::make_unique<LogCapture>();
le_impl_->connectability_state_ = ConnectabilityState::DISARMED;
@@ -886,8 +888,8 @@
ASSERT_TRUE(log_capture->Rewind()->Find("in unexpected state:ConnectabilityState::DISARMED"));
}
-TEST_F(LeImplTest, disarm_connectability_ARMING) {
- bluetooth::common::InitFlags::SetAllForTesting();
+// b/260917913
+TEST_F(LeImplTest, DISABLED_disarm_connectability_ARMING) {
std::unique_ptr<LogCapture> log_capture = std::make_unique<LogCapture>();
le_impl_->connectability_state_ = ConnectabilityState::ARMING;
@@ -899,8 +901,8 @@
ASSERT_TRUE(log_capture->Rewind()->Find("Le connection state machine armed state"));
}
-TEST_F(LeImplTest, disarm_connectability_ARMING_extended) {
- bluetooth::common::InitFlags::SetAllForTesting();
+// b/260917913
+TEST_F(LeImplTest, DISABLED_disarm_connectability_ARMING_extended) {
std::unique_ptr<LogCapture> log_capture = std::make_unique<LogCapture>();
le_impl_->connectability_state_ = ConnectabilityState::ARMING;
@@ -914,8 +916,8 @@
ASSERT_TRUE(log_capture->Rewind()->Find("Le connection state machine armed state"));
}
-TEST_F(LeImplTest, disarm_connectability_ARMED) {
- bluetooth::common::InitFlags::SetAllForTesting();
+// b/260917913
+TEST_F(LeImplTest, DISABLED_disarm_connectability_ARMED) {
std::unique_ptr<LogCapture> log_capture = std::make_unique<LogCapture>();
le_impl_->connectability_state_ = ConnectabilityState::ARMED;
@@ -928,8 +930,8 @@
ASSERT_TRUE(log_capture->Rewind()->Find("Disarming LE connection state machine with create connection"));
}
-TEST_F(LeImplTest, disarm_connectability_ARMED_extended) {
- bluetooth::common::InitFlags::SetAllForTesting();
+// b/260917913
+TEST_F(LeImplTest, DISABLED_disarm_connectability_ARMED_extended) {
std::unique_ptr<LogCapture> log_capture = std::make_unique<LogCapture>();
le_impl_->connectability_state_ = ConnectabilityState::ARMED;
@@ -943,8 +945,8 @@
ASSERT_TRUE(log_capture->Rewind()->Find("Disarming LE connection state machine with create connection"));
}
-TEST_F(LeImplTest, disarm_connectability_DISARMING) {
- bluetooth::common::InitFlags::SetAllForTesting();
+// b/260917913
+TEST_F(LeImplTest, DISABLED_disarm_connectability_DISARMING) {
std::unique_ptr<LogCapture> log_capture = std::make_unique<LogCapture>();
le_impl_->connectability_state_ = ConnectabilityState::DISARMING;
@@ -957,8 +959,8 @@
ASSERT_TRUE(log_capture->Rewind()->Find("in unexpected state:ConnectabilityState::DISARMING"));
}
-TEST_F(LeImplTest, disarm_connectability_DISARMING_extended) {
- bluetooth::common::InitFlags::SetAllForTesting();
+// b/260917913
+TEST_F(LeImplTest, DISABLED_disarm_connectability_DISARMING_extended) {
std::unique_ptr<LogCapture> log_capture = std::make_unique<LogCapture>();
le_impl_->connectability_state_ = ConnectabilityState::DISARMING;
@@ -972,8 +974,8 @@
ASSERT_TRUE(log_capture->Rewind()->Find("in unexpected state:ConnectabilityState::DISARMING"));
}
-TEST_F(LeImplTest, register_with_address_manager__AddressPolicyPublicAddress) {
- bluetooth::common::InitFlags::SetAllForTesting();
+// b/260917913
+TEST_F(LeImplTest, DISABLED_register_with_address_manager__AddressPolicyPublicAddress) {
std::unique_ptr<LogCapture> log_capture = std::make_unique<LogCapture>();
set_privacy_policy_for_initiator_address(fixed_address_, LeAddressManager::AddressPolicy::USE_PUBLIC_ADDRESS);
@@ -994,8 +996,8 @@
ASSERT_TRUE(log_capture->Rewind()->Find("Client unregistered"));
}
-TEST_F(LeImplTest, register_with_address_manager__AddressPolicyStaticAddress) {
- bluetooth::common::InitFlags::SetAllForTesting();
+// b/260917913
+TEST_F(LeImplTest, DISABLED_register_with_address_manager__AddressPolicyStaticAddress) {
std::unique_ptr<LogCapture> log_capture = std::make_unique<LogCapture>();
set_privacy_policy_for_initiator_address(fixed_address_, LeAddressManager::AddressPolicy::USE_STATIC_ADDRESS);
@@ -1016,8 +1018,8 @@
ASSERT_TRUE(log_capture->Rewind()->Find("Client unregistered"));
}
-TEST_F(LeImplTest, register_with_address_manager__AddressPolicyNonResolvableAddress) {
- bluetooth::common::InitFlags::SetAllForTesting();
+// b/260917913
+TEST_F(LeImplTest, DISABLED_register_with_address_manager__AddressPolicyNonResolvableAddress) {
std::unique_ptr<LogCapture> log_capture = std::make_unique<LogCapture>();
set_privacy_policy_for_initiator_address(fixed_address_, LeAddressManager::AddressPolicy::USE_NON_RESOLVABLE_ADDRESS);
@@ -1038,8 +1040,8 @@
ASSERT_TRUE(log_capture->Rewind()->Find("Client unregistered"));
}
-TEST_F(LeImplTest, register_with_address_manager__AddressPolicyResolvableAddress) {
- bluetooth::common::InitFlags::SetAllForTesting();
+// b/260917913
+TEST_F(LeImplTest, DISABLED_register_with_address_manager__AddressPolicyResolvableAddress) {
std::unique_ptr<LogCapture> log_capture = std::make_unique<LogCapture>();
set_privacy_policy_for_initiator_address(fixed_address_, LeAddressManager::AddressPolicy::USE_RESOLVABLE_ADDRESS);
@@ -1060,9 +1062,8 @@
ASSERT_TRUE(log_capture->Rewind()->Find("Client unregistered"));
}
-TEST_F(LeImplTest, add_device_to_resolving_list) {
- bluetooth::common::InitFlags::SetAllForTesting();
-
+// b/260920739
+TEST_F(LeImplTest, DISABLED_add_device_to_resolving_list) {
// Some kind of privacy policy must be set for LeAddressManager to operate properly
set_privacy_policy_for_initiator_address(fixed_address_, LeAddressManager::AddressPolicy::USE_PUBLIC_ADDRESS);
// Let LeAddressManager::resume_registered_clients execute
@@ -1133,8 +1134,6 @@
}
TEST_F(LeImplTest, add_device_to_resolving_list__SupportsBlePrivacy) {
- bluetooth::common::InitFlags::SetAllForTesting();
-
controller_->supports_ble_privacy_ = true;
// Some kind of privacy policy must be set for LeAddressManager to operate properly
@@ -1228,11 +1227,8 @@
}
TEST_F(LeImplTest, on_le_event__CONNECTION_COMPLETE_CENTRAL) {
- bluetooth::common::InitFlags::SetAllForTesting();
+ EXPECT_CALL(mock_le_connection_callbacks_, OnLeConnectSuccess(_, _)).Times(1);
set_random_device_address_policy();
-
- EXPECT_CALL(mock_le_connection_callbacks_, OnLeConnectSuccess(::testing::_, ::testing::_)).Times(1);
-
auto command = LeConnectionCompleteBuilder::Create(
ErrorCode::SUCCESS,
kHciHandle,
@@ -1243,22 +1239,15 @@
0x0000,
0x0011,
ClockAccuracy::PPM_30);
- auto bytes = std::make_shared<std::vector<uint8_t>>();
- BitInserter bi(*bytes);
- command->Serialize(bi);
+ auto bytes = Serialize<LeConnectionCompleteBuilder>(std::move(command));
auto view = CreateLeEventView<hci::LeConnectionCompleteView>(bytes);
ASSERT_TRUE(view.IsValid());
le_impl_->on_le_event(view);
}
TEST_F(LeImplTest, on_le_event__CONNECTION_COMPLETE_PERIPHERAL) {
- bluetooth::common::InitFlags::SetAllForTesting();
+ EXPECT_CALL(mock_le_connection_callbacks_, OnLeConnectSuccess(_, _)).Times(1);
set_random_device_address_policy();
-
- EXPECT_CALL(mock_le_connection_callbacks_, OnLeConnectSuccess(::testing::_, ::testing::_)).Times(1);
-
- auto bytes = std::make_shared<std::vector<uint8_t>>();
- BitInserter bi(*bytes);
auto command = LeConnectionCompleteBuilder::Create(
ErrorCode::SUCCESS,
kHciHandle,
@@ -1269,18 +1258,15 @@
0x0000,
0x0011,
ClockAccuracy::PPM_30);
- command->Serialize(bi);
+ auto bytes = Serialize<LeConnectionCompleteBuilder>(std::move(command));
auto view = CreateLeEventView<hci::LeConnectionCompleteView>(bytes);
ASSERT_TRUE(view.IsValid());
le_impl_->on_le_event(view);
}
TEST_F(LeImplTest, on_le_event__ENHANCED_CONNECTION_COMPLETE_CENTRAL) {
- bluetooth::common::InitFlags::SetAllForTesting();
+ EXPECT_CALL(mock_le_connection_callbacks_, OnLeConnectSuccess(_, _)).Times(1);
set_random_device_address_policy();
-
- EXPECT_CALL(mock_le_connection_callbacks_, OnLeConnectSuccess(::testing::_, ::testing::_)).Times(1);
-
auto command = LeEnhancedConnectionCompleteBuilder::Create(
ErrorCode::SUCCESS,
kHciHandle,
@@ -1293,20 +1279,15 @@
0x0000,
0x0011,
ClockAccuracy::PPM_30);
- auto bytes = std::make_shared<std::vector<uint8_t>>();
- BitInserter bi(*bytes);
- command->Serialize(bi);
+ auto bytes = Serialize<LeEnhancedConnectionCompleteBuilder>(std::move(command));
auto view = CreateLeEventView<hci::LeEnhancedConnectionCompleteView>(bytes);
ASSERT_TRUE(view.IsValid());
le_impl_->on_le_event(view);
}
TEST_F(LeImplTest, on_le_event__ENHANCED_CONNECTION_COMPLETE_PERIPHERAL) {
- EXPECT_CALL(mock_le_connection_callbacks_, OnLeConnectSuccess(::testing::_, ::testing::_)).Times(1);
-
- bluetooth::common::InitFlags::SetAllForTesting();
+ EXPECT_CALL(mock_le_connection_callbacks_, OnLeConnectSuccess(_, _)).Times(1);
set_random_device_address_policy();
-
auto command = LeEnhancedConnectionCompleteBuilder::Create(
ErrorCode::SUCCESS,
kHciHandle,
@@ -1319,9 +1300,7 @@
0x0000,
0x0011,
ClockAccuracy::PPM_30);
- auto bytes = std::make_shared<std::vector<uint8_t>>();
- BitInserter bi(*bytes);
- command->Serialize(bi);
+ auto bytes = Serialize<LeEnhancedConnectionCompleteBuilder>(std::move(command));
auto view = CreateLeEventView<hci::LeEnhancedConnectionCompleteView>(bytes);
ASSERT_TRUE(view.IsValid());
le_impl_->on_le_event(view);
@@ -1340,11 +1319,9 @@
}
TEST_F(LeImplWithConnectionTest, on_le_event__PHY_UPDATE_COMPLETE) {
- bluetooth::common::InitFlags::SetAllForTesting();
- set_random_device_address_policy();
- hci::ErrorCode hci_status;
- hci::PhyType tx_phy;
- hci::PhyType rx_phy;
+ hci::ErrorCode hci_status{ErrorCode::STATUS_UNKNOWN};
+ hci::PhyType tx_phy{0};
+ hci::PhyType rx_phy{0};
// Send a phy update
{
@@ -1354,10 +1331,8 @@
tx_phy = static_cast<PhyType>(_tx_phy);
rx_phy = static_cast<PhyType>(_rx_phy);
});
- auto command = LePhyUpdateCompleteBuilder::Create(ErrorCode::SUCCESS, kHciHandle, 0x01, 0x01);
- auto bytes = std::make_shared<std::vector<uint8_t>>();
- BitInserter bi(*bytes);
- command->Serialize(bi);
+ auto command = LePhyUpdateCompleteBuilder::Create(ErrorCode::SUCCESS, kHciHandle, 0x01, 0x02);
+ auto bytes = Serialize<LePhyUpdateCompleteBuilder>(std::move(command));
auto view = CreateLeEventView<hci::LePhyUpdateCompleteView>(bytes);
ASSERT_TRUE(view.IsValid());
le_impl_->on_le_event(view);
@@ -1366,12 +1341,10 @@
sync_handler();
ASSERT_EQ(ErrorCode::SUCCESS, hci_status);
ASSERT_EQ(PhyType::LE_1M, tx_phy);
+ ASSERT_EQ(PhyType::LE_2M, rx_phy);
}
TEST_F(LeImplWithConnectionTest, on_le_event__DATA_LENGTH_CHANGE) {
- bluetooth::common::InitFlags::SetAllForTesting();
- set_random_device_address_policy();
-
uint16_t tx_octets{0};
uint16_t tx_time{0};
uint16_t rx_octets{0};
@@ -1387,9 +1360,7 @@
rx_time = _rx_time;
});
auto command = LeDataLengthChangeBuilder::Create(kHciHandle, 0x1234, 0x5678, 0x9abc, 0xdef0);
- auto bytes = std::make_shared<std::vector<uint8_t>>();
- BitInserter bi(*bytes);
- command->Serialize(bi);
+ auto bytes = Serialize<LeDataLengthChangeBuilder>(std::move(command));
auto view = CreateLeEventView<hci::LeDataLengthChangeView>(bytes);
ASSERT_TRUE(view.IsValid());
le_impl_->on_le_event(view);
@@ -1403,15 +1374,10 @@
}
TEST_F(LeImplWithConnectionTest, on_le_event__REMOTE_CONNECTION_PARAMETER_REQUEST) {
- bluetooth::common::InitFlags::SetAllForTesting();
- set_random_device_address_policy();
-
// Send a remote connection parameter request
- auto bytes = std::make_shared<std::vector<uint8_t>>();
- BitInserter bi(*bytes);
auto command = hci::LeRemoteConnectionParameterRequestBuilder::Create(
kHciHandle, kIntervalMin, kIntervalMax, kLatency, kTimeout);
- command->Serialize(bi);
+ auto bytes = Serialize<LeRemoteConnectionParameterRequestBuilder>(std::move(command));
{
auto view = CreateLeEventView<hci::LeRemoteConnectionParameterRequestView>(bytes);
ASSERT_TRUE(view.IsValid());
@@ -1432,6 +1398,99 @@
ASSERT_EQ(kTimeout, view.GetTimeout());
}
+// b/260920739
+TEST_F(LeImplRegisteredWithAddressManagerTest, DISABLED_clear_resolving_list) {
+ le_impl_->clear_resolving_list();
+ ASSERT_EQ(3UL, le_impl_->le_address_manager_->NumberCachedCommands());
+
+ sync_handler(); // Allow |LeAddressManager::pause_registered_clients| to complete
+ sync_handler(); // Allow |LeAddressManager::handle_next_command| to complete
+
+ ASSERT_EQ(1UL, hci_layer_->NumberOfQueuedCommands());
+ {
+ auto view = CreateLeSecurityCommandView<LeSetAddressResolutionEnableView>(hci_layer_->DequeueCommandBytes());
+ ASSERT_TRUE(view.IsValid());
+ ASSERT_EQ(Enable::DISABLED, view.GetAddressResolutionEnable());
+ le_impl_->le_address_manager_->OnCommandComplete(
+ ReturnCommandComplete(OpCode::LE_SET_ADDRESS_RESOLUTION_ENABLE, ErrorCode::SUCCESS));
+ }
+
+ sync_handler(); // Allow |LeAddressManager::check_cached_commands| to complete
+ ASSERT_EQ(1UL, hci_layer_->NumberOfQueuedCommands());
+ {
+ auto view = CreateLeSecurityCommandView<LeClearResolvingListView>(hci_layer_->DequeueCommandBytes());
+ ASSERT_TRUE(view.IsValid());
+ le_impl_->le_address_manager_->OnCommandComplete(
+ ReturnCommandComplete(OpCode::LE_CLEAR_RESOLVING_LIST, ErrorCode::SUCCESS));
+ }
+
+ sync_handler(); // Allow |LeAddressManager::handle_next_command| to complete
+ ASSERT_EQ(1UL, hci_layer_->NumberOfQueuedCommands());
+ {
+ auto view = CreateLeSecurityCommandView<LeSetAddressResolutionEnableView>(hci_layer_->DequeueCommandBytes());
+ ASSERT_TRUE(view.IsValid());
+ ASSERT_EQ(Enable::ENABLED, view.GetAddressResolutionEnable());
+ le_impl_->le_address_manager_->OnCommandComplete(
+ ReturnCommandComplete(OpCode::LE_SET_ADDRESS_RESOLUTION_ENABLE, ErrorCode::SUCCESS));
+ }
+ ASSERT_TRUE(hci_layer_->IsPacketQueueEmpty());
+}
+
+TEST_F(LeImplWithConnectionTest, HACK_get_handle) {
+ sync_handler();
+
+ ASSERT_EQ(kHciHandle, le_impl_->HACK_get_handle(remote_address_));
+}
+
+TEST_F(LeImplTest, on_le_connection_canceled_on_pause) {
+ set_random_device_address_policy();
+ le_impl_->pause_connection = true;
+ le_impl_->on_le_connection_canceled_on_pause();
+ ASSERT_TRUE(le_impl_->arm_on_resume_);
+ ASSERT_EQ(ConnectabilityState::DISARMED, le_impl_->connectability_state_);
+}
+
+TEST_F(LeImplTest, on_create_connection_timeout) {
+ EXPECT_CALL(mock_le_connection_callbacks_, OnLeConnectFail(_, ErrorCode::CONNECTION_ACCEPT_TIMEOUT)).Times(1);
+ le_impl_->create_connection_timeout_alarms_.emplace(
+ std::piecewise_construct,
+ std::forward_as_tuple(
+ remote_public_address_with_type_.GetAddress(), remote_public_address_with_type_.GetAddressType()),
+ std::forward_as_tuple(handler_));
+ le_impl_->on_create_connection_timeout(remote_public_address_with_type_);
+ sync_handler();
+ ASSERT_TRUE(le_impl_->create_connection_timeout_alarms_.empty());
+}
+
+// b/260917913
+TEST_F(LeImplTest, DISABLED_on_common_le_connection_complete__NoPriorConnection) {
+ auto log_capture = std::make_unique<LogCapture>();
+ le_impl_->on_common_le_connection_complete(remote_public_address_with_type_);
+ ASSERT_TRUE(le_impl_->connecting_le_.empty());
+ ASSERT_TRUE(log_capture->Rewind()->Find("No prior connection request for"));
+}
+
+TEST_F(LeImplTest, cancel_connect) {
+ le_impl_->create_connection_timeout_alarms_.emplace(
+ std::piecewise_construct,
+ std::forward_as_tuple(
+ remote_public_address_with_type_.GetAddress(), remote_public_address_with_type_.GetAddressType()),
+ std::forward_as_tuple(handler_));
+ le_impl_->cancel_connect(remote_public_address_with_type_);
+ sync_handler();
+ ASSERT_TRUE(le_impl_->create_connection_timeout_alarms_.empty());
+}
+
+TEST_F(LeImplTest, set_le_suggested_default_data_parameters) {
+ le_impl_->set_le_suggested_default_data_parameters(kLength, kTime);
+ sync_handler();
+ auto view =
+ CreateLeConnectionManagementCommandView<LeWriteSuggestedDefaultDataLengthView>(hci_layer_->DequeueCommandBytes());
+ ASSERT_TRUE(view.IsValid());
+ ASSERT_EQ(kLength, view.GetTxOctets());
+ ASSERT_EQ(kTime, view.GetTxTime());
+}
+
} // namespace acl_manager
} // namespace hci
} // namespace bluetooth
diff --git a/system/gd/hci/acl_manager/round_robin_scheduler_test.cc b/system/gd/hci/acl_manager/round_robin_scheduler_test.cc
index 9d43c0f..a8bd3d7 100644
--- a/system/gd/hci/acl_manager/round_robin_scheduler_test.cc
+++ b/system/gd/hci/acl_manager/round_robin_scheduler_test.cc
@@ -35,6 +35,7 @@
namespace bluetooth {
namespace hci {
namespace acl_manager {
+namespace {
class TestController : public Controller {
public:
@@ -136,8 +137,9 @@
packet_count_--;
if (packet_count_ == 0) {
- packet_promise_->set_value();
- packet_promise_ = nullptr;
+ std::promise<void>* prom = packet_promise_.release();
+ prom->set_value();
+ delete prom;
}
}
@@ -152,7 +154,7 @@
}
void SetPacketFuture(uint16_t count) {
- ASSERT_LOG(packet_promise_ == nullptr, "Promises, Promises, ... Only one at a time.");
+ ASSERT_EQ(packet_promise_, nullptr) << "Promises, Promises, ... Only one at a time.";
packet_count_ = count;
packet_promise_ = std::make_unique<std::promise<void>>();
packet_future_ = std::make_unique<std::future<void>>(packet_promise_->get_future());
@@ -185,7 +187,7 @@
auto connection_queue = std::make_shared<AclConnection::Queue>(10);
round_robin_scheduler_->Register(RoundRobinScheduler::ConnectionType::CLASSIC, handle, connection_queue);
- SetPacketFuture(2);
+ ASSERT_NO_FATAL_FAILURE(SetPacketFuture(2));
AclConnection::QueueUpEnd* queue_up_end = connection_queue->GetUpEnd();
std::vector<uint8_t> packet1 = {0x01, 0x02, 0x03};
std::vector<uint8_t> packet2 = {0x04, 0x05, 0x06};
@@ -209,7 +211,7 @@
round_robin_scheduler_->Register(RoundRobinScheduler::ConnectionType::CLASSIC, handle, connection_queue);
round_robin_scheduler_->Register(RoundRobinScheduler::ConnectionType::LE, le_handle, le_connection_queue);
- SetPacketFuture(2);
+ ASSERT_NO_FATAL_FAILURE(SetPacketFuture(2));
AclConnection::QueueUpEnd* queue_up_end = connection_queue->GetUpEnd();
AclConnection::QueueUpEnd* le_queue_up_end = le_connection_queue->GetUpEnd();
std::vector<uint8_t> packet = {0x01, 0x02, 0x03};
@@ -232,7 +234,7 @@
auto connection_queue = std::make_shared<AclConnection::Queue>(15);
round_robin_scheduler_->Register(RoundRobinScheduler::ConnectionType::CLASSIC, handle, connection_queue);
- SetPacketFuture(10);
+ ASSERT_NO_FATAL_FAILURE(SetPacketFuture(10));
AclConnection::QueueUpEnd* queue_up_end = connection_queue->GetUpEnd();
for (uint8_t i = 0; i < 15; i++) {
std::vector<uint8_t> packet = {0x01, 0x02, 0x03, i};
@@ -246,7 +248,7 @@
}
ASSERT_EQ(round_robin_scheduler_->GetCredits(), 0);
- SetPacketFuture(5);
+ ASSERT_NO_FATAL_FAILURE(SetPacketFuture(5));
controller_->SendCompletedAclPacketsCallback(0x01, 10);
sync_handler();
packet_future_->wait();
@@ -276,7 +278,7 @@
auto le_connection_queue1 = std::make_shared<AclConnection::Queue>(10);
auto le_connection_queue2 = std::make_shared<AclConnection::Queue>(10);
- SetPacketFuture(18);
+ ASSERT_NO_FATAL_FAILURE(SetPacketFuture(18));
AclConnection::QueueUpEnd* queue_up_end1 = connection_queue1->GetUpEnd();
AclConnection::QueueUpEnd* queue_up_end2 = connection_queue2->GetUpEnd();
AclConnection::QueueUpEnd* le_queue_up_end1 = le_connection_queue1->GetUpEnd();
@@ -333,7 +335,7 @@
round_robin_scheduler_->Register(RoundRobinScheduler::ConnectionType::CLASSIC, handle, connection_queue);
round_robin_scheduler_->Register(RoundRobinScheduler::ConnectionType::LE, le_handle, le_connection_queue);
- SetPacketFuture(5);
+ ASSERT_NO_FATAL_FAILURE(SetPacketFuture(5));
AclConnection::QueueUpEnd* queue_up_end = connection_queue->GetUpEnd();
AclConnection::QueueUpEnd* le_queue_up_end = le_connection_queue->GetUpEnd();
std::vector<uint8_t> packet(controller_->hci_mtu_, 0xff);
@@ -379,7 +381,8 @@
round_robin_scheduler_->Register(RoundRobinScheduler::ConnectionType::CLASSIC, handle, connection_queue);
round_robin_scheduler_->Register(RoundRobinScheduler::ConnectionType::LE, le_handle, le_connection_queue);
- SetPacketFuture(controller_->le_max_acl_packet_credits_ + controller_->max_acl_packet_credits_);
+ ASSERT_NO_FATAL_FAILURE(
+ SetPacketFuture(controller_->le_max_acl_packet_credits_ + controller_->max_acl_packet_credits_));
AclConnection::QueueUpEnd* queue_up_end = connection_queue->GetUpEnd();
AclConnection::QueueUpEnd* le_queue_up_end = le_connection_queue->GetUpEnd();
std::vector<uint8_t> huge_packet(2000);
@@ -410,6 +413,7 @@
round_robin_scheduler_->Unregister(le_handle);
}
+} // namespace
} // namespace acl_manager
} // namespace hci
} // namespace bluetooth
diff --git a/system/gd/hci/acl_manager_test.cc b/system/gd/hci/acl_manager_test.cc
index 73e3228..e4cdcae 100644
--- a/system/gd/hci/acl_manager_test.cc
+++ b/system/gd/hci/acl_manager_test.cc
@@ -16,19 +16,20 @@
#include "hci/acl_manager.h"
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
#include <algorithm>
#include <chrono>
#include <future>
#include <map>
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
-
#include "common/bind.h"
#include "hci/address.h"
#include "hci/class_of_device.h"
#include "hci/controller.h"
#include "hci/hci_layer.h"
+#include "hci/hci_layer_fake.h"
#include "os/thread.h"
#include "packet/raw_builder.h"
@@ -43,37 +44,14 @@
using packet::PacketView;
using packet::RawBuilder;
-constexpr std::chrono::seconds kTimeout = std::chrono::seconds(2);
+constexpr auto kTimeout = std::chrono::seconds(2);
+constexpr auto kShortTimeout = std::chrono::milliseconds(100);
constexpr uint16_t kScanIntervalFast = 0x0060;
constexpr uint16_t kScanWindowFast = 0x0030;
constexpr uint16_t kScanIntervalSlow = 0x0800;
constexpr uint16_t kScanWindowSlow = 0x0030;
const AddressWithType empty_address_with_type = hci::AddressWithType();
-PacketView<kLittleEndian> GetPacketView(std::unique_ptr<packet::BasePacketBuilder> packet) {
- auto bytes = std::make_shared<std::vector<uint8_t>>();
- BitInserter i(*bytes);
- bytes->reserve(packet->size());
- packet->Serialize(i);
- return packet::PacketView<packet::kLittleEndian>(bytes);
-}
-
-std::unique_ptr<BasePacketBuilder> NextPayload(uint16_t handle) {
- static uint32_t packet_number = 1;
- auto payload = std::make_unique<RawBuilder>();
- payload->AddOctets2(6); // L2CAP PDU size
- payload->AddOctets2(2); // L2CAP CID
- payload->AddOctets2(handle);
- payload->AddOctets4(packet_number++);
- return std::move(payload);
-}
-
-std::unique_ptr<AclBuilder> NextAclPacket(uint16_t handle) {
- PacketBoundaryFlag packet_boundary_flag = PacketBoundaryFlag::FIRST_AUTOMATICALLY_FLUSHABLE;
- BroadcastFlag broadcast_flag = BroadcastFlag::POINT_TO_POINT;
- return AclBuilder::Create(handle, packet_boundary_flag, broadcast_flag, NextPayload(handle));
-}
-
class TestController : public Controller {
public:
void RegisterCompletedAclPacketsCallback(
@@ -118,200 +96,6 @@
void ListDependencies(ModuleList* list) const {}
};
-class TestHciLayer : public HciLayer {
- public:
- void EnqueueCommand(
- std::unique_ptr<CommandBuilder> command,
- common::ContextualOnceCallback<void(CommandStatusView)> on_status) override {
- command_queue_.push(std::move(command));
- command_status_callbacks.push_back(std::move(on_status));
- if (command_promise_ != nullptr) {
- command_promise_->set_value();
- command_promise_.reset();
- }
- }
-
- void EnqueueCommand(
- std::unique_ptr<CommandBuilder> command,
- common::ContextualOnceCallback<void(CommandCompleteView)> on_complete) override {
- command_queue_.push(std::move(command));
- command_complete_callbacks.push_back(std::move(on_complete));
- if (command_promise_ != nullptr) {
- command_promise_->set_value();
- command_promise_.reset();
- }
- }
-
- void SetCommandFuture() {
- ASSERT_LOG(command_promise_ == nullptr, "Promises, Promises, ... Only one at a time.");
- command_promise_ = std::make_unique<std::promise<void>>();
- command_future_ = std::make_unique<std::future<void>>(command_promise_->get_future());
- }
-
- CommandView GetLastCommand() {
- if (command_queue_.size() == 0) {
- return CommandView::Create(PacketView<kLittleEndian>(std::make_shared<std::vector<uint8_t>>()));
- }
- auto last = std::move(command_queue_.front());
- command_queue_.pop();
- return CommandView::Create(GetPacketView(std::move(last)));
- }
-
- ConnectionManagementCommandView GetCommand(OpCode op_code) {
- if (command_future_ != nullptr) {
- auto result = command_future_->wait_for(std::chrono::milliseconds(1000));
- EXPECT_NE(std::future_status::timeout, result);
- }
- if (command_queue_.empty()) {
- return ConnectionManagementCommandView::Create(AclCommandView::Create(
- CommandView::Create(PacketView<kLittleEndian>(std::make_shared<std::vector<uint8_t>>()))));
- }
- CommandView command_packet_view = GetLastCommand();
- ConnectionManagementCommandView command =
- ConnectionManagementCommandView::Create(AclCommandView::Create(command_packet_view));
- EXPECT_TRUE(command.IsValid());
- EXPECT_EQ(command.GetOpCode(), op_code);
-
- return command;
- }
-
- ConnectionManagementCommandView GetLastCommand(OpCode op_code) {
- if (!command_queue_.empty() && command_future_ != nullptr) {
- command_future_.reset();
- command_promise_.reset();
- } else if (command_future_ != nullptr) {
- auto result = command_future_->wait_for(std::chrono::milliseconds(1000));
- EXPECT_NE(std::future_status::timeout, result);
- }
- if (command_queue_.empty()) {
- return ConnectionManagementCommandView::Create(AclCommandView::Create(
- CommandView::Create(PacketView<kLittleEndian>(std::make_shared<std::vector<uint8_t>>()))));
- }
- CommandView command_packet_view = GetLastCommand();
- ConnectionManagementCommandView command =
- ConnectionManagementCommandView::Create(AclCommandView::Create(command_packet_view));
- EXPECT_TRUE(command.IsValid());
- EXPECT_EQ(command.GetOpCode(), op_code);
-
- return command;
- }
-
- void RegisterEventHandler(EventCode event_code, common::ContextualCallback<void(EventView)> event_handler) override {
- registered_events_[event_code] = event_handler;
- }
-
- void UnregisterEventHandler(EventCode event_code) override {
- registered_events_.erase(event_code);
- }
-
- void RegisterLeEventHandler(SubeventCode subevent_code,
- common::ContextualCallback<void(LeMetaEventView)> event_handler) override {
- registered_le_events_[subevent_code] = event_handler;
- }
-
- void UnregisterLeEventHandler(SubeventCode subevent_code) override {
- registered_le_events_.erase(subevent_code);
- }
-
- void IncomingEvent(std::unique_ptr<EventBuilder> event_builder) {
- auto packet = GetPacketView(std::move(event_builder));
- EventView event = EventView::Create(packet);
- ASSERT_TRUE(event.IsValid());
- EventCode event_code = event.GetEventCode();
- ASSERT_NE(registered_events_.find(event_code), registered_events_.end()) << EventCodeText(event_code);
- registered_events_[event_code].Invoke(event);
- }
-
- void IncomingLeMetaEvent(std::unique_ptr<LeMetaEventBuilder> event_builder) {
- auto packet = GetPacketView(std::move(event_builder));
- EventView event = EventView::Create(packet);
- LeMetaEventView meta_event_view = LeMetaEventView::Create(event);
- EXPECT_TRUE(meta_event_view.IsValid());
- SubeventCode subevent_code = meta_event_view.GetSubeventCode();
- EXPECT_TRUE(registered_le_events_.find(subevent_code) != registered_le_events_.end());
- registered_le_events_[subevent_code].Invoke(meta_event_view);
- }
-
- void IncomingAclData(uint16_t handle) {
- os::Handler* hci_handler = GetHandler();
- auto* queue_end = acl_queue_.GetDownEnd();
- std::promise<void> promise;
- auto future = promise.get_future();
- queue_end->RegisterEnqueue(hci_handler,
- common::Bind(
- [](decltype(queue_end) queue_end, uint16_t handle, std::promise<void> promise) {
- auto packet = GetPacketView(NextAclPacket(handle));
- AclView acl2 = AclView::Create(packet);
- queue_end->UnregisterEnqueue();
- promise.set_value();
- return std::make_unique<AclView>(acl2);
- },
- queue_end, handle, common::Passed(std::move(promise))));
- auto status = future.wait_for(kTimeout);
- ASSERT_EQ(status, std::future_status::ready);
- }
-
- void AssertNoOutgoingAclData() {
- auto queue_end = acl_queue_.GetDownEnd();
- EXPECT_EQ(queue_end->TryDequeue(), nullptr);
- }
-
- void CommandCompleteCallback(EventView event) {
- CommandCompleteView complete_view = CommandCompleteView::Create(event);
- ASSERT_TRUE(complete_view.IsValid());
- std::move(command_complete_callbacks.front()).Invoke(complete_view);
- command_complete_callbacks.pop_front();
- }
-
- void CommandStatusCallback(EventView event) {
- CommandStatusView status_view = CommandStatusView::Create(event);
- ASSERT_TRUE(status_view.IsValid());
- std::move(command_status_callbacks.front()).Invoke(status_view);
- command_status_callbacks.pop_front();
- }
-
- PacketView<kLittleEndian> OutgoingAclData() {
- auto queue_end = acl_queue_.GetDownEnd();
- std::unique_ptr<AclBuilder> received;
- do {
- received = queue_end->TryDequeue();
- } while (received == nullptr);
-
- return GetPacketView(std::move(received));
- }
-
- BidiQueueEnd<AclBuilder, AclView>* GetAclQueueEnd() override {
- return acl_queue_.GetUpEnd();
- }
-
- void ListDependencies(ModuleList* list) const {}
- void Start() override {
- RegisterEventHandler(EventCode::COMMAND_COMPLETE,
- GetHandler()->BindOn(this, &TestHciLayer::CommandCompleteCallback));
- RegisterEventHandler(EventCode::COMMAND_STATUS, GetHandler()->BindOn(this, &TestHciLayer::CommandStatusCallback));
- }
- void Stop() override {}
-
- void Disconnect(uint16_t handle, ErrorCode reason) override {
- GetHandler()->Post(common::BindOnce(&TestHciLayer::do_disconnect, common::Unretained(this), handle, reason));
- }
-
- private:
- std::map<EventCode, common::ContextualCallback<void(EventView)>> registered_events_;
- std::map<SubeventCode, common::ContextualCallback<void(LeMetaEventView)>> registered_le_events_;
- std::list<common::ContextualOnceCallback<void(CommandCompleteView)>> command_complete_callbacks;
- std::list<common::ContextualOnceCallback<void(CommandStatusView)>> command_status_callbacks;
- BidiQueue<AclView, AclBuilder> acl_queue_{3 /* TODO: Set queue depth */};
-
- std::queue<std::unique_ptr<CommandBuilder>> command_queue_;
- std::unique_ptr<std::promise<void>> command_promise_;
- std::unique_ptr<std::future<void>> command_future_;
-
- void do_disconnect(uint16_t handle, ErrorCode reason) {
- HciLayer::Disconnect(handle, reason);
- }
-};
-
class AclManagerNoCallbacksTest : public ::testing::Test {
protected:
void SetUp() override {
@@ -321,7 +105,6 @@
fake_registry_.InjectTestModule(&Controller::Factory, test_controller_);
client_handler_ = fake_registry_.GetTestModuleHandler(&HciLayer::Factory);
ASSERT_NE(client_handler_, nullptr);
- test_hci_layer_->SetCommandFuture();
fake_registry_.Start<AclManager>(&thread_);
acl_manager_ = static_cast<AclManager*>(fake_registry_.GetModuleUnderTest(&AclManager::Factory));
Address::FromString("A1:A2:A3:A4:A5:A6", remote);
@@ -337,21 +120,29 @@
minimum_rotation_time,
maximum_rotation_time);
- auto set_random_address_packet = LeSetRandomAddressView::Create(
- LeAdvertisingCommandView::Create(test_hci_layer_->GetCommand(OpCode::LE_SET_RANDOM_ADDRESS)));
+ auto set_random_address_packet =
+ LeSetRandomAddressView::Create(LeAdvertisingCommandView::Create(
+ GetConnectionManagementCommand(OpCode::LE_SET_RANDOM_ADDRESS)));
ASSERT_TRUE(set_random_address_packet.IsValid());
- my_initiating_address =
- AddressWithType(set_random_address_packet.GetRandomAddress(), AddressType::RANDOM_DEVICE_ADDRESS);
- // Verify LE Set Random Address was sent during setup
- test_hci_layer_->GetLastCommand(OpCode::LE_SET_RANDOM_ADDRESS);
+ my_initiating_address = AddressWithType(
+ set_random_address_packet.GetRandomAddress(), AddressType::RANDOM_DEVICE_ADDRESS);
test_hci_layer_->IncomingEvent(LeSetRandomAddressCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
}
void TearDown() override {
+ // Invalid mutex exception is raised if the connections
+ // are cleared after the AclConnectionInterface is deleted
+ // through fake_registry_.
+ mock_connection_callback_.Clear();
+ mock_le_connection_callbacks_.Clear();
fake_registry_.SynchronizeModuleHandler(&AclManager::Factory, std::chrono::milliseconds(20));
fake_registry_.StopAll();
}
+ void sync_client_handler() {
+ ASSERT(thread_.GetReactor()->WaitForIdle(std::chrono::seconds(2)));
+ }
+
TestModuleRegistry fake_registry_;
TestHciLayer* test_hci_layer_ = nullptr;
TestController* test_controller_ = nullptr;
@@ -398,6 +189,15 @@
ASSERT_EQ(status, std::future_status::ready);
}
+ ConnectionManagementCommandView GetConnectionManagementCommand(OpCode op_code) {
+ auto base_command = test_hci_layer_->GetCommand();
+ ConnectionManagementCommandView command =
+ ConnectionManagementCommandView::Create(AclCommandView::Create(base_command));
+ EXPECT_TRUE(command.IsValid());
+ EXPECT_EQ(command.GetOpCode(), op_code);
+ return command;
+ }
+
class MockConnectionCallback : public ConnectionCallbacks {
public:
void OnConnectSuccess(std::unique_ptr<ClassicAclConnection> connection) override {
@@ -408,6 +208,11 @@
connection_promise_.reset();
}
}
+
+ void Clear() {
+ connections_.clear();
+ }
+
MOCK_METHOD(void, OnConnectFail, (Address, ErrorCode reason), (override));
MOCK_METHOD(void, HACK_OnEscoConnectRequest, (Address, ClassOfDevice), (override));
@@ -426,6 +231,11 @@
le_connection_promise_.reset();
}
}
+
+ void Clear() {
+ le_connections_.clear();
+ }
+
MOCK_METHOD(void, OnLeConnectFail, (AddressWithType, ErrorCode reason), (override));
std::list<std::shared_ptr<LeAclConnection>> le_connections_;
@@ -451,9 +261,9 @@
acl_manager_->CreateConnection(remote);
// Wait for the connection request
- auto last_command = test_hci_layer_->GetCommand(OpCode::CREATE_CONNECTION);
+ auto last_command = GetConnectionManagementCommand(OpCode::CREATE_CONNECTION);
while (!last_command.IsValid()) {
- last_command = test_hci_layer_->GetCommand(OpCode::CREATE_CONNECTION);
+ last_command = GetConnectionManagementCommand(OpCode::CREATE_CONNECTION);
}
EXPECT_CALL(mock_connection_management_callbacks_, OnRoleChange(hci::ErrorCode::SUCCESS, Role::CENTRAL));
@@ -470,19 +280,17 @@
}
void TearDown() override {
+ // Invalid mutex exception is raised if the connection
+ // is cleared after the AclConnectionInterface is deleted
+ // through fake_registry_.
+ mock_connection_callback_.Clear();
+ mock_le_connection_callbacks_.Clear();
+ connection_.reset();
fake_registry_.SynchronizeModuleHandler(&HciLayer::Factory, std::chrono::milliseconds(20));
fake_registry_.SynchronizeModuleHandler(&AclManager::Factory, std::chrono::milliseconds(20));
fake_registry_.StopAll();
}
- void sync_client_handler() {
- std::promise<void> promise;
- auto future = promise.get_future();
- client_handler_->Post(common::BindOnce(&std::promise<void>::set_value, common::Unretained(&promise)));
- auto future_status = future.wait_for(std::chrono::seconds(1));
- EXPECT_EQ(future_status, std::future_status::ready);
- }
-
uint16_t handle_;
std::shared_ptr<ClassicAclConnection> connection_;
@@ -532,30 +340,15 @@
TEST_F(AclManagerTest, startup_teardown) {}
-TEST_F(AclManagerNoCallbacksTest, acl_connection_before_registered_callbacks) {
- ClassOfDevice class_of_device;
-
- test_hci_layer_->IncomingEvent(
- ConnectionRequestBuilder::Create(remote, class_of_device, ConnectionRequestLinkType::ACL));
- fake_registry_.SynchronizeModuleHandler(&HciLayer::Factory, std::chrono::milliseconds(20));
- fake_registry_.SynchronizeModuleHandler(&AclManager::Factory, std::chrono::milliseconds(20));
- fake_registry_.SynchronizeModuleHandler(&HciLayer::Factory, std::chrono::milliseconds(20));
- CommandView command = CommandView::Create(test_hci_layer_->GetLastCommand());
- EXPECT_TRUE(command.IsValid());
- OpCode op_code = command.GetOpCode();
- EXPECT_EQ(op_code, OpCode::REJECT_CONNECTION_REQUEST);
-}
-
TEST_F(AclManagerTest, invoke_registered_callback_connection_complete_success) {
uint16_t handle = 1;
- test_hci_layer_->SetCommandFuture();
acl_manager_->CreateConnection(remote);
// Wait for the connection request
- auto last_command = test_hci_layer_->GetCommand(OpCode::CREATE_CONNECTION);
+ auto last_command = GetConnectionManagementCommand(OpCode::CREATE_CONNECTION);
while (!last_command.IsValid()) {
- last_command = test_hci_layer_->GetCommand(OpCode::CREATE_CONNECTION);
+ last_command = GetConnectionManagementCommand(OpCode::CREATE_CONNECTION);
}
auto first_connection = GetConnectionFuture();
@@ -573,13 +366,12 @@
TEST_F(AclManagerTest, invoke_registered_callback_connection_complete_fail) {
uint16_t handle = 0x123;
- test_hci_layer_->SetCommandFuture();
acl_manager_->CreateConnection(remote);
// Wait for the connection request
- auto last_command = test_hci_layer_->GetCommand(OpCode::CREATE_CONNECTION);
+ auto last_command = GetConnectionManagementCommand(OpCode::CREATE_CONNECTION);
while (!last_command.IsValid()) {
- last_command = test_hci_layer_->GetCommand(OpCode::CREATE_CONNECTION);
+ last_command = GetConnectionManagementCommand(OpCode::CREATE_CONNECTION);
}
EXPECT_CALL(mock_connection_callback_, OnConnectFail(remote, ErrorCode::PAGE_TIMEOUT));
@@ -596,12 +388,10 @@
AclManagerTest::SetUp();
remote_with_type_ = AddressWithType(remote, AddressType::PUBLIC_DEVICE_ADDRESS);
- test_hci_layer_->SetCommandFuture();
acl_manager_->CreateLeConnection(remote_with_type_, true);
- test_hci_layer_->GetCommand(OpCode::LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST);
+ GetConnectionManagementCommand(OpCode::LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST);
test_hci_layer_->IncomingEvent(LeAddDeviceToFilterAcceptListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
- test_hci_layer_->SetCommandFuture();
- auto packet = test_hci_layer_->GetCommand(OpCode::LE_CREATE_CONNECTION);
+ auto packet = GetConnectionManagementCommand(OpCode::LE_CREATE_CONNECTION);
auto le_connection_management_command_view =
LeConnectionManagementCommandView::Create(AclCommandView::Create(packet));
auto command_view = LeCreateConnectionView::Create(le_connection_management_command_view);
@@ -621,7 +411,7 @@
test_hci_layer_->IncomingLeMetaEvent(LeConnectionCompleteBuilder::Create(
ErrorCode::SUCCESS,
handle_,
- Role::PERIPHERAL,
+ Role::CENTRAL,
AddressType::PUBLIC_DEVICE_ADDRESS,
remote,
0x0100,
@@ -629,8 +419,7 @@
0x0C80,
ClockAccuracy::PPM_30));
- test_hci_layer_->SetCommandFuture();
- test_hci_layer_->GetCommand(OpCode::LE_REMOVE_DEVICE_FROM_FILTER_ACCEPT_LIST);
+ GetConnectionManagementCommand(OpCode::LE_REMOVE_DEVICE_FROM_FILTER_ACCEPT_LIST);
test_hci_layer_->IncomingEvent(LeRemoveDeviceFromFilterAcceptListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
auto first_connection_status = first_connection.wait_for(kTimeout);
@@ -640,19 +429,17 @@
}
void TearDown() override {
+ // Invalid mutex exception is raised if the connection
+ // is cleared after the AclConnectionInterface is deleted
+ // through fake_registry_.
+ mock_connection_callback_.Clear();
+ mock_le_connection_callbacks_.Clear();
+ connection_.reset();
fake_registry_.SynchronizeModuleHandler(&HciLayer::Factory, std::chrono::milliseconds(20));
fake_registry_.SynchronizeModuleHandler(&AclManager::Factory, std::chrono::milliseconds(20));
fake_registry_.StopAll();
}
- void sync_client_handler() {
- std::promise<void> promise;
- auto future = promise.get_future();
- client_handler_->Post(common::BindOnce(&std::promise<void>::set_value, common::Unretained(&promise)));
- auto future_status = future.wait_for(std::chrono::seconds(1));
- EXPECT_EQ(future_status, std::future_status::ready);
- }
-
uint16_t handle_ = 0x123;
std::shared_ptr<LeAclConnection> connection_;
AddressWithType remote_with_type_;
@@ -686,12 +473,10 @@
TEST_F(AclManagerTest, invoke_registered_callback_le_connection_complete_fail) {
AddressWithType remote_with_type(remote, AddressType::PUBLIC_DEVICE_ADDRESS);
- test_hci_layer_->SetCommandFuture();
acl_manager_->CreateLeConnection(remote_with_type, true);
- test_hci_layer_->GetLastCommand(OpCode::LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST);
+ GetConnectionManagementCommand(OpCode::LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST);
test_hci_layer_->IncomingEvent(LeAddDeviceToFilterAcceptListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
- test_hci_layer_->SetCommandFuture();
- auto packet = test_hci_layer_->GetLastCommand(OpCode::LE_CREATE_CONNECTION);
+ auto packet = GetConnectionManagementCommand(OpCode::LE_CREATE_CONNECTION);
auto le_connection_management_command_view =
LeConnectionManagementCommandView::Create(AclCommandView::Create(packet));
auto command_view = LeCreateConnectionView::Create(le_connection_management_command_view);
@@ -707,10 +492,11 @@
EXPECT_CALL(mock_le_connection_callbacks_,
OnLeConnectFail(remote_with_type, ErrorCode::CONNECTION_REJECTED_LIMITED_RESOURCES));
+
test_hci_layer_->IncomingLeMetaEvent(LeConnectionCompleteBuilder::Create(
ErrorCode::CONNECTION_REJECTED_LIMITED_RESOURCES,
0x123,
- Role::PERIPHERAL,
+ Role::CENTRAL,
AddressType::PUBLIC_DEVICE_ADDRESS,
remote,
0x0100,
@@ -718,8 +504,7 @@
0x0011,
ClockAccuracy::PPM_30));
- test_hci_layer_->SetCommandFuture();
- packet = test_hci_layer_->GetLastCommand(OpCode::LE_REMOVE_DEVICE_FROM_FILTER_ACCEPT_LIST);
+ packet = GetConnectionManagementCommand(OpCode::LE_REMOVE_DEVICE_FROM_FILTER_ACCEPT_LIST);
le_connection_management_command_view = LeConnectionManagementCommandView::Create(AclCommandView::Create(packet));
auto remove_command_view = LeRemoveDeviceFromFilterAcceptListView::Create(le_connection_management_command_view);
ASSERT_TRUE(remove_command_view.IsValid());
@@ -728,16 +513,14 @@
TEST_F(AclManagerTest, cancel_le_connection) {
AddressWithType remote_with_type(remote, AddressType::PUBLIC_DEVICE_ADDRESS);
- test_hci_layer_->SetCommandFuture();
acl_manager_->CreateLeConnection(remote_with_type, true);
- test_hci_layer_->GetLastCommand(OpCode::LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST);
+ GetConnectionManagementCommand(OpCode::LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST);
test_hci_layer_->IncomingEvent(LeAddDeviceToFilterAcceptListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
- test_hci_layer_->SetCommandFuture();
- test_hci_layer_->GetLastCommand(OpCode::LE_CREATE_CONNECTION);
+ GetConnectionManagementCommand(OpCode::LE_CREATE_CONNECTION);
+ test_hci_layer_->IncomingEvent(LeCreateConnectionStatusBuilder::Create(ErrorCode::SUCCESS, 0x01));
- test_hci_layer_->SetCommandFuture();
acl_manager_->CancelLeConnect(remote_with_type);
- auto packet = test_hci_layer_->GetLastCommand(OpCode::LE_CREATE_CONNECTION_CANCEL);
+ auto packet = GetConnectionManagementCommand(OpCode::LE_CREATE_CONNECTION_CANCEL);
auto le_connection_management_command_view =
LeConnectionManagementCommandView::Create(AclCommandView::Create(packet));
auto command_view = LeCreateConnectionCancelView::Create(le_connection_management_command_view);
@@ -747,7 +530,7 @@
test_hci_layer_->IncomingLeMetaEvent(LeConnectionCompleteBuilder::Create(
ErrorCode::UNKNOWN_CONNECTION,
0x123,
- Role::PERIPHERAL,
+ Role::CENTRAL,
AddressType::PUBLIC_DEVICE_ADDRESS,
remote,
0x0100,
@@ -755,8 +538,7 @@
0x0011,
ClockAccuracy::PPM_30));
- test_hci_layer_->SetCommandFuture();
- packet = test_hci_layer_->GetLastCommand(OpCode::LE_REMOVE_DEVICE_FROM_FILTER_ACCEPT_LIST);
+ packet = GetConnectionManagementCommand(OpCode::LE_REMOVE_DEVICE_FROM_FILTER_ACCEPT_LIST);
le_connection_management_command_view = LeConnectionManagementCommandView::Create(AclCommandView::Create(packet));
auto remove_command_view = LeRemoveDeviceFromFilterAcceptListView::Create(le_connection_management_command_view);
ASSERT_TRUE(remove_command_view.IsValid());
@@ -766,31 +548,32 @@
TEST_F(AclManagerTest, create_connection_with_fast_mode) {
AddressWithType remote_with_type(remote, AddressType::PUBLIC_DEVICE_ADDRESS);
- test_hci_layer_->SetCommandFuture();
acl_manager_->CreateLeConnection(remote_with_type, true);
- test_hci_layer_->GetLastCommand(OpCode::LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST);
- test_hci_layer_->IncomingEvent(LeAddDeviceToFilterAcceptListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
- test_hci_layer_->SetCommandFuture();
- auto packet = test_hci_layer_->GetLastCommand(OpCode::LE_CREATE_CONNECTION);
+ GetConnectionManagementCommand(OpCode::LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST);
+ test_hci_layer_->IncomingEvent(
+ LeAddDeviceToFilterAcceptListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
+
+ auto packet = GetConnectionManagementCommand(OpCode::LE_CREATE_CONNECTION);
auto command_view =
LeCreateConnectionView::Create(LeConnectionManagementCommandView::Create(AclCommandView::Create(packet)));
ASSERT_TRUE(command_view.IsValid());
ASSERT_EQ(command_view.GetLeScanInterval(), kScanIntervalFast);
ASSERT_EQ(command_view.GetLeScanWindow(), kScanWindowFast);
test_hci_layer_->IncomingEvent(LeCreateConnectionStatusBuilder::Create(ErrorCode::SUCCESS, 0x01));
+
auto first_connection = GetLeConnectionFuture();
test_hci_layer_->IncomingLeMetaEvent(LeConnectionCompleteBuilder::Create(
ErrorCode::SUCCESS,
0x00,
- Role::PERIPHERAL,
+ Role::CENTRAL,
AddressType::PUBLIC_DEVICE_ADDRESS,
remote,
0x0100,
0x0010,
0x0C80,
ClockAccuracy::PPM_30));
- test_hci_layer_->SetCommandFuture();
- test_hci_layer_->GetCommand(OpCode::LE_REMOVE_DEVICE_FROM_FILTER_ACCEPT_LIST);
+
+ GetConnectionManagementCommand(OpCode::LE_REMOVE_DEVICE_FROM_FILTER_ACCEPT_LIST);
test_hci_layer_->IncomingEvent(LeRemoveDeviceFromFilterAcceptListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
auto first_connection_status = first_connection.wait_for(kTimeout);
ASSERT_EQ(first_connection_status, std::future_status::ready);
@@ -798,12 +581,10 @@
TEST_F(AclManagerTest, create_connection_with_slow_mode) {
AddressWithType remote_with_type(remote, AddressType::PUBLIC_DEVICE_ADDRESS);
- test_hci_layer_->SetCommandFuture();
acl_manager_->CreateLeConnection(remote_with_type, false);
- test_hci_layer_->GetLastCommand(OpCode::LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST);
+ GetConnectionManagementCommand(OpCode::LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST);
test_hci_layer_->IncomingEvent(LeAddDeviceToFilterAcceptListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
- test_hci_layer_->SetCommandFuture();
- auto packet = test_hci_layer_->GetLastCommand(OpCode::LE_CREATE_CONNECTION);
+ auto packet = GetConnectionManagementCommand(OpCode::LE_CREATE_CONNECTION);
auto command_view =
LeCreateConnectionView::Create(LeConnectionManagementCommandView::Create(AclCommandView::Create(packet)));
ASSERT_TRUE(command_view.IsValid());
@@ -814,15 +595,14 @@
test_hci_layer_->IncomingLeMetaEvent(LeConnectionCompleteBuilder::Create(
ErrorCode::SUCCESS,
0x00,
- Role::PERIPHERAL,
+ Role::CENTRAL,
AddressType::PUBLIC_DEVICE_ADDRESS,
remote,
0x0100,
0x0010,
0x0C80,
ClockAccuracy::PPM_30));
- test_hci_layer_->SetCommandFuture();
- test_hci_layer_->GetCommand(OpCode::LE_REMOVE_DEVICE_FROM_FILTER_ACCEPT_LIST);
+ GetConnectionManagementCommand(OpCode::LE_REMOVE_DEVICE_FROM_FILTER_ACCEPT_LIST);
test_hci_layer_->IncomingEvent(LeRemoveDeviceFromFilterAcceptListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
auto first_connection_status = first_connection.wait_for(kTimeout);
ASSERT_EQ(first_connection_status, std::future_status::ready);
@@ -867,19 +647,25 @@
uint16_t connection_interval = (connection_interval_max + connection_interval_min) / 2;
uint16_t connection_latency = 0x0001;
uint16_t supervision_timeout = 0x0A00;
- test_hci_layer_->SetCommandFuture();
- connection_->LeConnectionUpdate(connection_interval_min, connection_interval_max, connection_latency,
- supervision_timeout, 0x10, 0x20);
- auto update_packet = test_hci_layer_->GetCommand(OpCode::LE_CONNECTION_UPDATE);
+ connection_->LeConnectionUpdate(
+ connection_interval_min,
+ connection_interval_max,
+ connection_latency,
+ supervision_timeout,
+ 0x10,
+ 0x20);
+ auto update_packet = GetConnectionManagementCommand(OpCode::LE_CONNECTION_UPDATE);
auto update_view =
LeConnectionUpdateView::Create(LeConnectionManagementCommandView::Create(AclCommandView::Create(update_packet)));
ASSERT_TRUE(update_view.IsValid());
EXPECT_EQ(update_view.GetConnectionHandle(), handle_);
+ test_hci_layer_->IncomingEvent(LeConnectionUpdateStatusBuilder::Create(ErrorCode::SUCCESS, 0x1));
EXPECT_CALL(
mock_le_connection_management_callbacks_,
OnConnectionUpdate(hci_status, connection_interval, connection_latency, supervision_timeout));
test_hci_layer_->IncomingLeMetaEvent(LeConnectionUpdateCompleteBuilder::Create(
ErrorCode::SUCCESS, handle_, connection_interval, connection_latency, supervision_timeout));
+ sync_client_handler();
}
TEST_F(AclManagerWithLeConnectionTest, invoke_registered_callback_le_disconnect) {
@@ -890,9 +676,10 @@
auto reason = ErrorCode::REMOTE_USER_TERMINATED_CONNECTION;
EXPECT_CALL(mock_le_connection_management_callbacks_, OnDisconnection(reason));
test_hci_layer_->Disconnect(handle_, reason);
+ sync_client_handler();
}
-TEST_F(AclManagerWithLeConnectionTest, DISABLED_invoke_registered_callback_le_disconnect_data_race) {
+TEST_F(AclManagerWithLeConnectionTest, invoke_registered_callback_le_disconnect_data_race) {
ASSERT_EQ(connection_->GetRemoteAddress(), remote_with_type_);
ASSERT_EQ(connection_->GetHandle(), handle_);
connection_->RegisterCallbacks(&mock_le_connection_management_callbacks_, client_handler_);
@@ -901,6 +688,7 @@
auto reason = ErrorCode::REMOTE_USER_TERMINATED_CONNECTION;
EXPECT_CALL(mock_le_connection_management_callbacks_, OnDisconnection(reason));
test_hci_layer_->Disconnect(handle_, reason);
+ sync_client_handler();
}
TEST_F(AclManagerWithLeConnectionTest, invoke_registered_callback_le_queue_disconnect) {
@@ -918,6 +706,7 @@
auto reason = ErrorCode::REMOTE_USER_TERMINATED_CONNECTION;
EXPECT_CALL(mock_connection_management_callbacks_, OnDisconnection(reason));
test_hci_layer_->Disconnect(handle_, reason);
+ sync_client_handler();
}
TEST_F(AclManagerWithConnectionTest, acl_send_data_one_connection) {
@@ -941,15 +730,15 @@
SendAclData(handle_, connection_->GetAclQueueEnd());
sent_packet = test_hci_layer_->OutgoingAclData();
- test_hci_layer_->SetCommandFuture();
auto reason = ErrorCode::AUTHENTICATION_FAILURE;
EXPECT_CALL(mock_connection_management_callbacks_, OnDisconnection(reason));
connection_->Disconnect(DisconnectReason::AUTHENTICATION_FAILURE);
- auto packet = test_hci_layer_->GetCommand(OpCode::DISCONNECT);
+ auto packet = GetConnectionManagementCommand(OpCode::DISCONNECT);
auto command_view = DisconnectView::Create(packet);
ASSERT_TRUE(command_view.IsValid());
ASSERT_EQ(command_view.GetConnectionHandle(), handle_);
test_hci_layer_->Disconnect(handle_, reason);
+ sync_client_handler();
}
TEST_F(AclManagerWithConnectionTest, acl_send_data_credits) {
@@ -969,12 +758,12 @@
test_controller_->CompletePackets(handle_, 1);
auto after_credits_sent_packet = test_hci_layer_->OutgoingAclData();
+ sync_client_handler();
}
TEST_F(AclManagerWithConnectionTest, send_switch_role) {
- test_hci_layer_->SetCommandFuture();
acl_manager_->SwitchRole(connection_->GetAddress(), Role::PERIPHERAL);
- auto packet = test_hci_layer_->GetCommand(OpCode::SWITCH_ROLE);
+ auto packet = GetConnectionManagementCommand(OpCode::SWITCH_ROLE);
auto command_view = SwitchRoleView::Create(packet);
ASSERT_TRUE(command_view.IsValid());
ASSERT_EQ(command_view.GetBdAddr(), connection_->GetAddress());
@@ -983,13 +772,13 @@
EXPECT_CALL(mock_connection_management_callbacks_, OnRoleChange(hci::ErrorCode::SUCCESS, Role::PERIPHERAL));
test_hci_layer_->IncomingEvent(
RoleChangeBuilder::Create(ErrorCode::SUCCESS, connection_->GetAddress(), Role::PERIPHERAL));
+ sync_client_handler();
}
TEST_F(AclManagerWithConnectionTest, send_write_default_link_policy_settings) {
- test_hci_layer_->SetCommandFuture();
uint16_t link_policy_settings = 0x05;
acl_manager_->WriteDefaultLinkPolicySettings(link_policy_settings);
- auto packet = test_hci_layer_->GetCommand(OpCode::WRITE_DEFAULT_LINK_POLICY_SETTINGS);
+ auto packet = GetConnectionManagementCommand(OpCode::WRITE_DEFAULT_LINK_POLICY_SETTINGS);
auto command_view = WriteDefaultLinkPolicySettingsView::Create(packet);
ASSERT_TRUE(command_view.IsValid());
ASSERT_EQ(command_view.GetDefaultLinkPolicySettings(), 0x05);
@@ -997,49 +786,52 @@
uint8_t num_packets = 1;
test_hci_layer_->IncomingEvent(
WriteDefaultLinkPolicySettingsCompleteBuilder::Create(num_packets, ErrorCode::SUCCESS));
+ sync_client_handler();
ASSERT_EQ(link_policy_settings, acl_manager_->ReadDefaultLinkPolicySettings());
}
TEST_F(AclManagerWithConnectionTest, send_authentication_requested) {
- test_hci_layer_->SetCommandFuture();
connection_->AuthenticationRequested();
- auto packet = test_hci_layer_->GetCommand(OpCode::AUTHENTICATION_REQUESTED);
+ auto packet = GetConnectionManagementCommand(OpCode::AUTHENTICATION_REQUESTED);
auto command_view = AuthenticationRequestedView::Create(packet);
ASSERT_TRUE(command_view.IsValid());
EXPECT_CALL(mock_connection_management_callbacks_, OnAuthenticationComplete);
- test_hci_layer_->IncomingEvent(AuthenticationCompleteBuilder::Create(ErrorCode::SUCCESS, handle_));
+ test_hci_layer_->IncomingEvent(
+ AuthenticationCompleteBuilder::Create(ErrorCode::SUCCESS, handle_));
+ sync_client_handler();
}
TEST_F(AclManagerWithConnectionTest, send_read_clock_offset) {
- test_hci_layer_->SetCommandFuture();
connection_->ReadClockOffset();
- auto packet = test_hci_layer_->GetCommand(OpCode::READ_CLOCK_OFFSET);
+ auto packet = GetConnectionManagementCommand(OpCode::READ_CLOCK_OFFSET);
auto command_view = ReadClockOffsetView::Create(packet);
ASSERT_TRUE(command_view.IsValid());
EXPECT_CALL(mock_connection_management_callbacks_, OnReadClockOffsetComplete(0x0123));
- test_hci_layer_->IncomingEvent(ReadClockOffsetCompleteBuilder::Create(ErrorCode::SUCCESS, handle_, 0x0123));
+ test_hci_layer_->IncomingEvent(
+ ReadClockOffsetCompleteBuilder::Create(ErrorCode::SUCCESS, handle_, 0x0123));
+ sync_client_handler();
}
TEST_F(AclManagerWithConnectionTest, send_hold_mode) {
- test_hci_layer_->SetCommandFuture();
connection_->HoldMode(0x0500, 0x0020);
- auto packet = test_hci_layer_->GetCommand(OpCode::HOLD_MODE);
+ auto packet = GetConnectionManagementCommand(OpCode::HOLD_MODE);
auto command_view = HoldModeView::Create(packet);
ASSERT_TRUE(command_view.IsValid());
ASSERT_EQ(command_view.GetHoldModeMaxInterval(), 0x0500);
ASSERT_EQ(command_view.GetHoldModeMinInterval(), 0x0020);
EXPECT_CALL(mock_connection_management_callbacks_, OnModeChange(ErrorCode::SUCCESS, Mode::HOLD, 0x0020));
- test_hci_layer_->IncomingEvent(ModeChangeBuilder::Create(ErrorCode::SUCCESS, handle_, Mode::HOLD, 0x0020));
+ test_hci_layer_->IncomingEvent(
+ ModeChangeBuilder::Create(ErrorCode::SUCCESS, handle_, Mode::HOLD, 0x0020));
+ sync_client_handler();
}
TEST_F(AclManagerWithConnectionTest, send_sniff_mode) {
- test_hci_layer_->SetCommandFuture();
connection_->SniffMode(0x0500, 0x0020, 0x0040, 0x0014);
- auto packet = test_hci_layer_->GetCommand(OpCode::SNIFF_MODE);
+ auto packet = GetConnectionManagementCommand(OpCode::SNIFF_MODE);
auto command_view = SniffModeView::Create(packet);
ASSERT_TRUE(command_view.IsValid());
ASSERT_EQ(command_view.GetSniffMaxInterval(), 0x0500);
@@ -1048,101 +840,109 @@
ASSERT_EQ(command_view.GetSniffTimeout(), 0x0014);
EXPECT_CALL(mock_connection_management_callbacks_, OnModeChange(ErrorCode::SUCCESS, Mode::SNIFF, 0x0028));
- test_hci_layer_->IncomingEvent(ModeChangeBuilder::Create(ErrorCode::SUCCESS, handle_, Mode::SNIFF, 0x0028));
+ test_hci_layer_->IncomingEvent(
+ ModeChangeBuilder::Create(ErrorCode::SUCCESS, handle_, Mode::SNIFF, 0x0028));
+ sync_client_handler();
}
TEST_F(AclManagerWithConnectionTest, send_exit_sniff_mode) {
- test_hci_layer_->SetCommandFuture();
connection_->ExitSniffMode();
- auto packet = test_hci_layer_->GetCommand(OpCode::EXIT_SNIFF_MODE);
+ auto packet = GetConnectionManagementCommand(OpCode::EXIT_SNIFF_MODE);
auto command_view = ExitSniffModeView::Create(packet);
ASSERT_TRUE(command_view.IsValid());
EXPECT_CALL(mock_connection_management_callbacks_, OnModeChange(ErrorCode::SUCCESS, Mode::ACTIVE, 0x00));
- test_hci_layer_->IncomingEvent(ModeChangeBuilder::Create(ErrorCode::SUCCESS, handle_, Mode::ACTIVE, 0x00));
+ test_hci_layer_->IncomingEvent(
+ ModeChangeBuilder::Create(ErrorCode::SUCCESS, handle_, Mode::ACTIVE, 0x00));
+ sync_client_handler();
}
TEST_F(AclManagerWithConnectionTest, send_qos_setup) {
- test_hci_layer_->SetCommandFuture();
connection_->QosSetup(ServiceType::BEST_EFFORT, 0x1234, 0x1233, 0x1232, 0x1231);
- auto packet = test_hci_layer_->GetCommand(OpCode::QOS_SETUP);
+ auto packet = GetConnectionManagementCommand(OpCode::QOS_SETUP);
auto command_view = QosSetupView::Create(packet);
ASSERT_TRUE(command_view.IsValid());
ASSERT_EQ(command_view.GetServiceType(), ServiceType::BEST_EFFORT);
- ASSERT_EQ(command_view.GetTokenRate(), 0x1234);
- ASSERT_EQ(command_view.GetPeakBandwidth(), 0x1233);
- ASSERT_EQ(command_view.GetLatency(), 0x1232);
- ASSERT_EQ(command_view.GetDelayVariation(), 0x1231);
+ ASSERT_EQ(command_view.GetTokenRate(), 0x1234u);
+ ASSERT_EQ(command_view.GetPeakBandwidth(), 0x1233u);
+ ASSERT_EQ(command_view.GetLatency(), 0x1232u);
+ ASSERT_EQ(command_view.GetDelayVariation(), 0x1231u);
EXPECT_CALL(mock_connection_management_callbacks_,
OnQosSetupComplete(ServiceType::BEST_EFFORT, 0x1234, 0x1233, 0x1232, 0x1231));
- test_hci_layer_->IncomingEvent(QosSetupCompleteBuilder::Create(ErrorCode::SUCCESS, handle_, ServiceType::BEST_EFFORT,
- 0x1234, 0x1233, 0x1232, 0x1231));
+ test_hci_layer_->IncomingEvent(QosSetupCompleteBuilder::Create(
+ ErrorCode::SUCCESS, handle_, ServiceType::BEST_EFFORT, 0x1234, 0x1233, 0x1232, 0x1231));
+ sync_client_handler();
}
TEST_F(AclManagerWithConnectionTest, send_flow_specification) {
- test_hci_layer_->SetCommandFuture();
- connection_->FlowSpecification(FlowDirection::OUTGOING_FLOW, ServiceType::BEST_EFFORT, 0x1234, 0x1233, 0x1232,
- 0x1231);
- auto packet = test_hci_layer_->GetCommand(OpCode::FLOW_SPECIFICATION);
+ connection_->FlowSpecification(
+ FlowDirection::OUTGOING_FLOW, ServiceType::BEST_EFFORT, 0x1234, 0x1233, 0x1232, 0x1231);
+ auto packet = GetConnectionManagementCommand(OpCode::FLOW_SPECIFICATION);
auto command_view = FlowSpecificationView::Create(packet);
ASSERT_TRUE(command_view.IsValid());
ASSERT_EQ(command_view.GetFlowDirection(), FlowDirection::OUTGOING_FLOW);
ASSERT_EQ(command_view.GetServiceType(), ServiceType::BEST_EFFORT);
- ASSERT_EQ(command_view.GetTokenRate(), 0x1234);
- ASSERT_EQ(command_view.GetTokenBucketSize(), 0x1233);
- ASSERT_EQ(command_view.GetPeakBandwidth(), 0x1232);
- ASSERT_EQ(command_view.GetAccessLatency(), 0x1231);
+ ASSERT_EQ(command_view.GetTokenRate(), 0x1234u);
+ ASSERT_EQ(command_view.GetTokenBucketSize(), 0x1233u);
+ ASSERT_EQ(command_view.GetPeakBandwidth(), 0x1232u);
+ ASSERT_EQ(command_view.GetAccessLatency(), 0x1231u);
EXPECT_CALL(mock_connection_management_callbacks_,
OnFlowSpecificationComplete(FlowDirection::OUTGOING_FLOW, ServiceType::BEST_EFFORT, 0x1234, 0x1233,
0x1232, 0x1231));
- test_hci_layer_->IncomingEvent(
- FlowSpecificationCompleteBuilder::Create(ErrorCode::SUCCESS, handle_, FlowDirection::OUTGOING_FLOW,
- ServiceType::BEST_EFFORT, 0x1234, 0x1233, 0x1232, 0x1231));
+ test_hci_layer_->IncomingEvent(FlowSpecificationCompleteBuilder::Create(
+ ErrorCode::SUCCESS,
+ handle_,
+ FlowDirection::OUTGOING_FLOW,
+ ServiceType::BEST_EFFORT,
+ 0x1234,
+ 0x1233,
+ 0x1232,
+ 0x1231));
+ sync_client_handler();
}
TEST_F(AclManagerWithConnectionTest, send_flush) {
- test_hci_layer_->SetCommandFuture();
connection_->Flush();
- auto packet = test_hci_layer_->GetCommand(OpCode::FLUSH);
+ auto packet = GetConnectionManagementCommand(OpCode::FLUSH);
auto command_view = FlushView::Create(packet);
ASSERT_TRUE(command_view.IsValid());
EXPECT_CALL(mock_connection_management_callbacks_, OnFlushOccurred());
test_hci_layer_->IncomingEvent(FlushOccurredBuilder::Create(handle_));
+ sync_client_handler();
}
TEST_F(AclManagerWithConnectionTest, send_role_discovery) {
- test_hci_layer_->SetCommandFuture();
connection_->RoleDiscovery();
- auto packet = test_hci_layer_->GetCommand(OpCode::ROLE_DISCOVERY);
+ auto packet = GetConnectionManagementCommand(OpCode::ROLE_DISCOVERY);
auto command_view = RoleDiscoveryView::Create(packet);
ASSERT_TRUE(command_view.IsValid());
EXPECT_CALL(mock_connection_management_callbacks_, OnRoleDiscoveryComplete(Role::CENTRAL));
uint8_t num_packets = 1;
- test_hci_layer_->IncomingEvent(
- RoleDiscoveryCompleteBuilder::Create(num_packets, ErrorCode::SUCCESS, handle_, Role::CENTRAL));
+ test_hci_layer_->IncomingEvent(RoleDiscoveryCompleteBuilder::Create(
+ num_packets, ErrorCode::SUCCESS, handle_, Role::CENTRAL));
+ sync_client_handler();
}
TEST_F(AclManagerWithConnectionTest, send_read_link_policy_settings) {
- test_hci_layer_->SetCommandFuture();
connection_->ReadLinkPolicySettings();
- auto packet = test_hci_layer_->GetCommand(OpCode::READ_LINK_POLICY_SETTINGS);
+ auto packet = GetConnectionManagementCommand(OpCode::READ_LINK_POLICY_SETTINGS);
auto command_view = ReadLinkPolicySettingsView::Create(packet);
ASSERT_TRUE(command_view.IsValid());
EXPECT_CALL(mock_connection_management_callbacks_, OnReadLinkPolicySettingsComplete(0x07));
uint8_t num_packets = 1;
- test_hci_layer_->IncomingEvent(
- ReadLinkPolicySettingsCompleteBuilder::Create(num_packets, ErrorCode::SUCCESS, handle_, 0x07));
+ test_hci_layer_->IncomingEvent(ReadLinkPolicySettingsCompleteBuilder::Create(
+ num_packets, ErrorCode::SUCCESS, handle_, 0x07));
+ sync_client_handler();
}
TEST_F(AclManagerWithConnectionTest, send_write_link_policy_settings) {
- test_hci_layer_->SetCommandFuture();
connection_->WriteLinkPolicySettings(0x05);
- auto packet = test_hci_layer_->GetCommand(OpCode::WRITE_LINK_POLICY_SETTINGS);
+ auto packet = GetConnectionManagementCommand(OpCode::WRITE_LINK_POLICY_SETTINGS);
auto command_view = WriteLinkPolicySettingsView::Create(packet);
ASSERT_TRUE(command_view.IsValid());
ASSERT_EQ(command_view.GetLinkPolicySettings(), 0x05);
@@ -1150,12 +950,12 @@
uint8_t num_packets = 1;
test_hci_layer_->IncomingEvent(
WriteLinkPolicySettingsCompleteBuilder::Create(num_packets, ErrorCode::SUCCESS, handle_));
+ sync_client_handler();
}
TEST_F(AclManagerWithConnectionTest, send_sniff_subrating) {
- test_hci_layer_->SetCommandFuture();
connection_->SniffSubrating(0x1234, 0x1235, 0x1236);
- auto packet = test_hci_layer_->GetCommand(OpCode::SNIFF_SUBRATING);
+ auto packet = GetConnectionManagementCommand(OpCode::SNIFF_SUBRATING);
auto command_view = SniffSubratingView::Create(packet);
ASSERT_TRUE(command_view.IsValid());
ASSERT_EQ(command_view.GetMaximumLatency(), 0x1234);
@@ -1163,26 +963,27 @@
ASSERT_EQ(command_view.GetMinimumLocalTimeout(), 0x1236);
uint8_t num_packets = 1;
- test_hci_layer_->IncomingEvent(SniffSubratingCompleteBuilder::Create(num_packets, ErrorCode::SUCCESS, handle_));
+ test_hci_layer_->IncomingEvent(
+ SniffSubratingCompleteBuilder::Create(num_packets, ErrorCode::SUCCESS, handle_));
+ sync_client_handler();
}
TEST_F(AclManagerWithConnectionTest, send_read_automatic_flush_timeout) {
- test_hci_layer_->SetCommandFuture();
connection_->ReadAutomaticFlushTimeout();
- auto packet = test_hci_layer_->GetCommand(OpCode::READ_AUTOMATIC_FLUSH_TIMEOUT);
+ auto packet = GetConnectionManagementCommand(OpCode::READ_AUTOMATIC_FLUSH_TIMEOUT);
auto command_view = ReadAutomaticFlushTimeoutView::Create(packet);
ASSERT_TRUE(command_view.IsValid());
EXPECT_CALL(mock_connection_management_callbacks_, OnReadAutomaticFlushTimeoutComplete(0x07ff));
uint8_t num_packets = 1;
- test_hci_layer_->IncomingEvent(
- ReadAutomaticFlushTimeoutCompleteBuilder::Create(num_packets, ErrorCode::SUCCESS, handle_, 0x07ff));
+ test_hci_layer_->IncomingEvent(ReadAutomaticFlushTimeoutCompleteBuilder::Create(
+ num_packets, ErrorCode::SUCCESS, handle_, 0x07ff));
+ sync_client_handler();
}
TEST_F(AclManagerWithConnectionTest, send_write_automatic_flush_timeout) {
- test_hci_layer_->SetCommandFuture();
connection_->WriteAutomaticFlushTimeout(0x07FF);
- auto packet = test_hci_layer_->GetCommand(OpCode::WRITE_AUTOMATIC_FLUSH_TIMEOUT);
+ auto packet = GetConnectionManagementCommand(OpCode::WRITE_AUTOMATIC_FLUSH_TIMEOUT);
auto command_view = WriteAutomaticFlushTimeoutView::Create(packet);
ASSERT_TRUE(command_view.IsValid());
ASSERT_EQ(command_view.GetFlushTimeout(), 0x07FF);
@@ -1190,39 +991,39 @@
uint8_t num_packets = 1;
test_hci_layer_->IncomingEvent(
WriteAutomaticFlushTimeoutCompleteBuilder::Create(num_packets, ErrorCode::SUCCESS, handle_));
+ sync_client_handler();
}
TEST_F(AclManagerWithConnectionTest, send_read_transmit_power_level) {
- test_hci_layer_->SetCommandFuture();
connection_->ReadTransmitPowerLevel(TransmitPowerLevelType::CURRENT);
- auto packet = test_hci_layer_->GetCommand(OpCode::READ_TRANSMIT_POWER_LEVEL);
+ auto packet = GetConnectionManagementCommand(OpCode::READ_TRANSMIT_POWER_LEVEL);
auto command_view = ReadTransmitPowerLevelView::Create(packet);
ASSERT_TRUE(command_view.IsValid());
ASSERT_EQ(command_view.GetTransmitPowerLevelType(), TransmitPowerLevelType::CURRENT);
EXPECT_CALL(mock_connection_management_callbacks_, OnReadTransmitPowerLevelComplete(0x07));
uint8_t num_packets = 1;
- test_hci_layer_->IncomingEvent(
- ReadTransmitPowerLevelCompleteBuilder::Create(num_packets, ErrorCode::SUCCESS, handle_, 0x07));
+ test_hci_layer_->IncomingEvent(ReadTransmitPowerLevelCompleteBuilder::Create(
+ num_packets, ErrorCode::SUCCESS, handle_, 0x07));
+ sync_client_handler();
}
TEST_F(AclManagerWithConnectionTest, send_read_link_supervision_timeout) {
- test_hci_layer_->SetCommandFuture();
connection_->ReadLinkSupervisionTimeout();
- auto packet = test_hci_layer_->GetCommand(OpCode::READ_LINK_SUPERVISION_TIMEOUT);
+ auto packet = GetConnectionManagementCommand(OpCode::READ_LINK_SUPERVISION_TIMEOUT);
auto command_view = ReadLinkSupervisionTimeoutView::Create(packet);
ASSERT_TRUE(command_view.IsValid());
EXPECT_CALL(mock_connection_management_callbacks_, OnReadLinkSupervisionTimeoutComplete(0x5677));
uint8_t num_packets = 1;
- test_hci_layer_->IncomingEvent(
- ReadLinkSupervisionTimeoutCompleteBuilder::Create(num_packets, ErrorCode::SUCCESS, handle_, 0x5677));
+ test_hci_layer_->IncomingEvent(ReadLinkSupervisionTimeoutCompleteBuilder::Create(
+ num_packets, ErrorCode::SUCCESS, handle_, 0x5677));
+ sync_client_handler();
}
TEST_F(AclManagerWithConnectionTest, send_write_link_supervision_timeout) {
- test_hci_layer_->SetCommandFuture();
connection_->WriteLinkSupervisionTimeout(0x5678);
- auto packet = test_hci_layer_->GetCommand(OpCode::WRITE_LINK_SUPERVISION_TIMEOUT);
+ auto packet = GetConnectionManagementCommand(OpCode::WRITE_LINK_SUPERVISION_TIMEOUT);
auto command_view = WriteLinkSupervisionTimeoutView::Create(packet);
ASSERT_TRUE(command_view.IsValid());
ASSERT_EQ(command_view.GetLinkSupervisionTimeout(), 0x5678);
@@ -1230,37 +1031,37 @@
uint8_t num_packets = 1;
test_hci_layer_->IncomingEvent(
WriteLinkSupervisionTimeoutCompleteBuilder::Create(num_packets, ErrorCode::SUCCESS, handle_));
+ sync_client_handler();
}
TEST_F(AclManagerWithConnectionTest, send_read_failed_contact_counter) {
- test_hci_layer_->SetCommandFuture();
connection_->ReadFailedContactCounter();
- auto packet = test_hci_layer_->GetCommand(OpCode::READ_FAILED_CONTACT_COUNTER);
+ auto packet = GetConnectionManagementCommand(OpCode::READ_FAILED_CONTACT_COUNTER);
auto command_view = ReadFailedContactCounterView::Create(packet);
ASSERT_TRUE(command_view.IsValid());
EXPECT_CALL(mock_connection_management_callbacks_, OnReadFailedContactCounterComplete(0x00));
uint8_t num_packets = 1;
- test_hci_layer_->IncomingEvent(
- ReadFailedContactCounterCompleteBuilder::Create(num_packets, ErrorCode::SUCCESS, handle_, 0x00));
+ test_hci_layer_->IncomingEvent(ReadFailedContactCounterCompleteBuilder::Create(
+ num_packets, ErrorCode::SUCCESS, handle_, 0x00));
+ sync_client_handler();
}
TEST_F(AclManagerWithConnectionTest, send_reset_failed_contact_counter) {
- test_hci_layer_->SetCommandFuture();
connection_->ResetFailedContactCounter();
- auto packet = test_hci_layer_->GetCommand(OpCode::RESET_FAILED_CONTACT_COUNTER);
+ auto packet = GetConnectionManagementCommand(OpCode::RESET_FAILED_CONTACT_COUNTER);
auto command_view = ResetFailedContactCounterView::Create(packet);
ASSERT_TRUE(command_view.IsValid());
uint8_t num_packets = 1;
test_hci_layer_->IncomingEvent(
ResetFailedContactCounterCompleteBuilder::Create(num_packets, ErrorCode::SUCCESS, handle_));
+ sync_client_handler();
}
TEST_F(AclManagerWithConnectionTest, send_read_link_quality) {
- test_hci_layer_->SetCommandFuture();
connection_->ReadLinkQuality();
- auto packet = test_hci_layer_->GetCommand(OpCode::READ_LINK_QUALITY);
+ auto packet = GetConnectionManagementCommand(OpCode::READ_LINK_QUALITY);
auto command_view = ReadLinkQualityView::Create(packet);
ASSERT_TRUE(command_view.IsValid());
@@ -1268,12 +1069,12 @@
uint8_t num_packets = 1;
test_hci_layer_->IncomingEvent(
ReadLinkQualityCompleteBuilder::Create(num_packets, ErrorCode::SUCCESS, handle_, 0xa9));
+ sync_client_handler();
}
TEST_F(AclManagerWithConnectionTest, send_read_afh_channel_map) {
- test_hci_layer_->SetCommandFuture();
connection_->ReadAfhChannelMap();
- auto packet = test_hci_layer_->GetCommand(OpCode::READ_AFH_CHANNEL_MAP);
+ auto packet = GetConnectionManagementCommand(OpCode::READ_AFH_CHANNEL_MAP);
auto command_view = ReadAfhChannelMapView::Create(packet);
ASSERT_TRUE(command_view.IsValid());
std::array<uint8_t, 10> afh_channel_map = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09};
@@ -1281,34 +1082,36 @@
EXPECT_CALL(mock_connection_management_callbacks_,
OnReadAfhChannelMapComplete(AfhMode::AFH_ENABLED, afh_channel_map));
uint8_t num_packets = 1;
- test_hci_layer_->IncomingEvent(ReadAfhChannelMapCompleteBuilder::Create(num_packets, ErrorCode::SUCCESS, handle_,
- AfhMode::AFH_ENABLED, afh_channel_map));
+ test_hci_layer_->IncomingEvent(ReadAfhChannelMapCompleteBuilder::Create(
+ num_packets, ErrorCode::SUCCESS, handle_, AfhMode::AFH_ENABLED, afh_channel_map));
+ sync_client_handler();
}
TEST_F(AclManagerWithConnectionTest, send_read_rssi) {
- test_hci_layer_->SetCommandFuture();
connection_->ReadRssi();
- auto packet = test_hci_layer_->GetCommand(OpCode::READ_RSSI);
+ auto packet = GetConnectionManagementCommand(OpCode::READ_RSSI);
auto command_view = ReadRssiView::Create(packet);
ASSERT_TRUE(command_view.IsValid());
sync_client_handler();
EXPECT_CALL(mock_connection_management_callbacks_, OnReadRssiComplete(0x00));
uint8_t num_packets = 1;
- test_hci_layer_->IncomingEvent(ReadRssiCompleteBuilder::Create(num_packets, ErrorCode::SUCCESS, handle_, 0x00));
+ test_hci_layer_->IncomingEvent(
+ ReadRssiCompleteBuilder::Create(num_packets, ErrorCode::SUCCESS, handle_, 0x00));
+ sync_client_handler();
}
TEST_F(AclManagerWithConnectionTest, send_read_clock) {
- test_hci_layer_->SetCommandFuture();
connection_->ReadClock(WhichClock::LOCAL);
- auto packet = test_hci_layer_->GetCommand(OpCode::READ_CLOCK);
+ auto packet = GetConnectionManagementCommand(OpCode::READ_CLOCK);
auto command_view = ReadClockView::Create(packet);
ASSERT_TRUE(command_view.IsValid());
ASSERT_EQ(command_view.GetWhichClock(), WhichClock::LOCAL);
EXPECT_CALL(mock_connection_management_callbacks_, OnReadClockComplete(0x00002e6a, 0x0000));
uint8_t num_packets = 1;
- test_hci_layer_->IncomingEvent(
- ReadClockCompleteBuilder::Create(num_packets, ErrorCode::SUCCESS, handle_, 0x00002e6a, 0x0000));
+ test_hci_layer_->IncomingEvent(ReadClockCompleteBuilder::Create(
+ num_packets, ErrorCode::SUCCESS, handle_, 0x00002e6a, 0x0000));
+ sync_client_handler();
}
class AclManagerWithResolvableAddressTest : public AclManagerNoCallbacksTest {
@@ -1320,7 +1123,6 @@
fake_registry_.InjectTestModule(&Controller::Factory, test_controller_);
client_handler_ = fake_registry_.GetTestModuleHandler(&HciLayer::Factory);
ASSERT_NE(client_handler_, nullptr);
- test_hci_layer_->SetCommandFuture();
fake_registry_.Start<AclManager>(&thread_);
acl_manager_ = static_cast<AclManager*>(fake_registry_.GetModuleUnderTest(&AclManager::Factory));
Address::FromString("A1:A2:A3:A4:A5:A6", remote);
@@ -1338,28 +1140,22 @@
minimum_rotation_time,
maximum_rotation_time);
- test_hci_layer_->GetLastCommand(OpCode::LE_SET_RANDOM_ADDRESS);
+ GetConnectionManagementCommand(OpCode::LE_SET_RANDOM_ADDRESS);
test_hci_layer_->IncomingEvent(LeSetRandomAddressCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
}
};
TEST_F(AclManagerWithResolvableAddressTest, create_connection_cancel_fail) {
auto remote_with_type_ = AddressWithType(remote, AddressType::PUBLIC_DEVICE_ADDRESS);
- test_hci_layer_->SetCommandFuture();
acl_manager_->CreateLeConnection(remote_with_type_, true);
- // Set random address
- test_hci_layer_->GetLastCommand(OpCode::LE_SET_RANDOM_ADDRESS);
- test_hci_layer_->SetCommandFuture();
- test_hci_layer_->IncomingEvent(LeSetRandomAddressCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
-
// Add device to connect list
- test_hci_layer_->GetLastCommand(OpCode::LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST);
- test_hci_layer_->SetCommandFuture();
- test_hci_layer_->IncomingEvent(LeAddDeviceToFilterAcceptListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
+ GetConnectionManagementCommand(OpCode::LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST);
+ test_hci_layer_->IncomingEvent(
+ LeAddDeviceToFilterAcceptListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
// send create connection command
- test_hci_layer_->GetLastCommand(OpCode::LE_CREATE_CONNECTION);
+ GetConnectionManagementCommand(OpCode::LE_CREATE_CONNECTION);
test_hci_layer_->IncomingEvent(LeCreateConnectionStatusBuilder::Create(ErrorCode::SUCCESS, 0x01));
fake_registry_.SynchronizeModuleHandler(&HciLayer::Factory, std::chrono::milliseconds(20));
@@ -1370,11 +1166,10 @@
auto remote_with_type2 = AddressWithType(remote2, AddressType::PUBLIC_DEVICE_ADDRESS);
// create another connection
- test_hci_layer_->SetCommandFuture();
acl_manager_->CreateLeConnection(remote_with_type2, true);
// cancel previous connection
- test_hci_layer_->GetLastCommand(OpCode::LE_CREATE_CONNECTION_CANCEL);
+ GetConnectionManagementCommand(OpCode::LE_CREATE_CONNECTION_CANCEL);
// receive connection complete of first device
test_hci_layer_->IncomingLeMetaEvent(LeConnectionCompleteBuilder::Create(
@@ -1389,13 +1184,14 @@
ClockAccuracy::PPM_30));
// receive create connection cancel complete with ErrorCode::CONNECTION_ALREADY_EXISTS
- test_hci_layer_->SetCommandFuture();
test_hci_layer_->IncomingEvent(
LeCreateConnectionCancelCompleteBuilder::Create(0x01, ErrorCode::CONNECTION_ALREADY_EXISTS));
// Add another device to connect list
- test_hci_layer_->GetLastCommand(OpCode::LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST);
+ GetConnectionManagementCommand(OpCode::LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST);
test_hci_layer_->IncomingEvent(LeAddDeviceToFilterAcceptListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
+
+ // Sync events.
}
class AclManagerLifeCycleTest : public AclManagerNoCallbacksTest {
@@ -1412,9 +1208,8 @@
TEST_F(AclManagerLifeCycleTest, unregister_classic_after_create_connection) {
// Inject create connection
- test_hci_layer_->SetCommandFuture();
acl_manager_->CreateConnection(remote);
- auto connection_command = test_hci_layer_->GetCommand(OpCode::CREATE_CONNECTION);
+ auto connection_command = GetConnectionManagementCommand(OpCode::CREATE_CONNECTION);
// Unregister callbacks after sending connection request
auto promise = std::promise<void>();
@@ -1426,38 +1221,19 @@
auto connection_future = GetConnectionFuture();
test_hci_layer_->IncomingEvent(
ConnectionCompleteBuilder::Create(ErrorCode::SUCCESS, handle_, remote, LinkType::ACL, Enable::DISABLED));
- auto connection_future_status = connection_future.wait_for(kTimeout);
+
+ sync_client_handler();
+ auto connection_future_status = connection_future.wait_for(kShortTimeout);
ASSERT_NE(connection_future_status, std::future_status::ready);
}
-TEST_F(AclManagerLifeCycleTest, unregister_classic_before_connection_request) {
- ClassOfDevice class_of_device;
-
- // Unregister callbacks before receiving connection request
- auto promise = std::promise<void>();
- auto future = promise.get_future();
- acl_manager_->UnregisterCallbacks(&mock_connection_callback_, std::move(promise));
- future.get();
-
- // Inject peer sending connection request
- auto connection_future = GetConnectionFuture();
- test_hci_layer_->IncomingEvent(
- ConnectionRequestBuilder::Create(remote, class_of_device, ConnectionRequestLinkType::ACL));
- auto connection_future_status = connection_future.wait_for(kTimeout);
- ASSERT_NE(connection_future_status, std::future_status::ready);
-
- test_hci_layer_->GetLastCommand(OpCode::REJECT_CONNECTION_REQUEST);
-}
-
TEST_F(AclManagerLifeCycleTest, unregister_le_before_connection_complete) {
AddressWithType remote_with_type(remote, AddressType::PUBLIC_DEVICE_ADDRESS);
- test_hci_layer_->SetCommandFuture();
acl_manager_->CreateLeConnection(remote_with_type, true);
- test_hci_layer_->GetLastCommand(OpCode::LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST);
+ GetConnectionManagementCommand(OpCode::LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST);
test_hci_layer_->IncomingEvent(LeAddDeviceToFilterAcceptListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
- test_hci_layer_->SetCommandFuture();
- auto packet = test_hci_layer_->GetLastCommand(OpCode::LE_CREATE_CONNECTION);
+ auto packet = GetConnectionManagementCommand(OpCode::LE_CREATE_CONNECTION);
auto le_connection_management_command_view =
LeConnectionManagementCommandView::Create(AclCommandView::Create(packet));
auto command_view = LeCreateConnectionView::Create(le_connection_management_command_view);
@@ -1487,19 +1263,18 @@
0x0500,
ClockAccuracy::PPM_30));
- auto connection_future_status = connection_future.wait_for(kTimeout);
+ sync_client_handler();
+ auto connection_future_status = connection_future.wait_for(kShortTimeout);
ASSERT_NE(connection_future_status, std::future_status::ready);
}
TEST_F(AclManagerLifeCycleTest, unregister_le_before_enhanced_connection_complete) {
AddressWithType remote_with_type(remote, AddressType::PUBLIC_DEVICE_ADDRESS);
- test_hci_layer_->SetCommandFuture();
acl_manager_->CreateLeConnection(remote_with_type, true);
- test_hci_layer_->GetLastCommand(OpCode::LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST);
+ GetConnectionManagementCommand(OpCode::LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST);
test_hci_layer_->IncomingEvent(LeAddDeviceToFilterAcceptListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
- test_hci_layer_->SetCommandFuture();
- auto packet = test_hci_layer_->GetLastCommand(OpCode::LE_CREATE_CONNECTION);
+ auto packet = GetConnectionManagementCommand(OpCode::LE_CREATE_CONNECTION);
auto le_connection_management_command_view =
LeConnectionManagementCommandView::Create(AclCommandView::Create(packet));
auto command_view = LeCreateConnectionView::Create(le_connection_management_command_view);
@@ -1531,7 +1306,8 @@
0x0500,
ClockAccuracy::PPM_30));
- auto connection_future_status = connection_future.wait_for(kTimeout);
+ sync_client_handler();
+ auto connection_future_status = connection_future.wait_for(kShortTimeout);
ASSERT_NE(connection_future_status, std::future_status::ready);
}
diff --git a/system/gd/hci/acl_manager_unittest.cc b/system/gd/hci/acl_manager_unittest.cc
index 37d96db..31c71eb 100644
--- a/system/gd/hci/acl_manager_unittest.cc
+++ b/system/gd/hci/acl_manager_unittest.cc
@@ -21,11 +21,15 @@
#include <algorithm>
#include <chrono>
+#include <deque>
#include <future>
+#include <list>
#include <map>
#include "common/bind.h"
+#include "common/init_flags.h"
#include "hci/address.h"
+#include "hci/address_with_type.h"
#include "hci/class_of_device.h"
#include "hci/controller.h"
#include "hci/hci_layer.h"
@@ -45,9 +49,28 @@
using packet::PacketView;
using packet::RawBuilder;
-constexpr std::chrono::seconds kTimeout = std::chrono::seconds(2);
+namespace {
+constexpr char kLocalRandomAddressString[] = "D0:05:04:03:02:01";
+constexpr char kRemotePublicDeviceStringA[] = "11:A2:A3:A4:A5:A6";
+constexpr char kRemotePublicDeviceStringB[] = "11:B2:B3:B4:B5:B6";
+constexpr uint16_t kHciHandleA = 123;
+constexpr uint16_t kHciHandleB = 456;
+
+constexpr auto kMinimumRotationTime = std::chrono::milliseconds(7 * 60 * 1000);
+constexpr auto kMaximumRotationTime = std::chrono::milliseconds(15 * 60 * 1000);
+
const AddressWithType empty_address_with_type = hci::AddressWithType();
+struct {
+ Address address;
+ ClassOfDevice class_of_device;
+ const uint16_t handle;
+} remote_device[2] = {
+ {.address = {}, .class_of_device = {}, .handle = kHciHandleA},
+ {.address = {}, .class_of_device = {}, .handle = kHciHandleB},
+};
+} // namespace
+
PacketView<kLittleEndian> GetPacketView(std::unique_ptr<packet::BasePacketBuilder> packet) {
auto bytes = std::make_shared<std::vector<uint8_t>>();
BitInserter i(*bytes);
@@ -74,15 +97,6 @@
class TestController : public Controller {
public:
- void RegisterCompletedAclPacketsCallback(
- common::ContextualCallback<void(uint16_t /* handle */, uint16_t /* packets */)> cb) override {
- acl_cb_ = cb;
- }
-
- void UnregisterCompletedAclPacketsCallback() override {
- acl_cb_ = {};
- }
-
uint16_t GetAclPacketLength() const override {
return acl_buffer_length_;
}
@@ -102,18 +116,15 @@
return le_buffer_size;
}
- void CompletePackets(uint16_t handle, uint16_t packets) {
- acl_cb_.Invoke(handle, packets);
- }
-
- uint16_t acl_buffer_length_ = 1024;
- uint16_t total_acl_buffers_ = 2;
- common::ContextualCallback<void(uint16_t /* handle */, uint16_t /* packets */)> acl_cb_;
-
protected:
void Start() override {}
void Stop() override {}
void ListDependencies(ModuleList* list) const {}
+
+ private:
+ uint16_t acl_buffer_length_ = 1024;
+ uint16_t total_acl_buffers_ = 2;
+ common::ContextualCallback<void(uint16_t /* handle */, uint16_t /* packets */)> acl_cb_;
};
class TestHciLayer : public HciLayer {
@@ -123,10 +134,7 @@
common::ContextualOnceCallback<void(CommandStatusView)> on_status) override {
command_queue_.push(std::move(command));
command_status_callbacks.push_back(std::move(on_status));
- if (command_promise_ != nullptr) {
- command_promise_->set_value();
- command_promise_.reset();
- }
+ Notify();
}
void EnqueueCommand(
@@ -134,16 +142,18 @@
common::ContextualOnceCallback<void(CommandCompleteView)> on_complete) override {
command_queue_.push(std::move(command));
command_complete_callbacks.push_back(std::move(on_complete));
- if (command_promise_ != nullptr) {
- command_promise_->set_value();
- command_promise_.reset();
- }
+ Notify();
}
void SetCommandFuture() {
- ASSERT_TRUE(command_promise_ == nullptr) << "Promises, Promises, ... Only one at a time.";
- command_promise_ = std::make_unique<std::promise<void>>();
- command_future_ = std::make_unique<std::future<void>>(command_promise_->get_future());
+ ASSERT_EQ(hci_command_promise_, nullptr) << "Promises, Promises, ... Only one at a time.";
+ hci_command_promise_ = std::make_unique<std::promise<void>>();
+ command_future_ = std::make_unique<std::future<void>>(hci_command_promise_->get_future());
+ }
+
+ std::future<void> GetOutgoingCommandFuture() {
+ hci_command_promise_ = std::make_unique<std::promise<void>>();
+ return hci_command_promise_->get_future();
}
CommandView GetLastCommand() {
@@ -173,9 +183,10 @@
ConnectionManagementCommandView GetLastCommand(OpCode op_code) {
if (!command_queue_.empty() && command_future_ != nullptr) {
command_future_.reset();
- command_promise_.reset();
+ hci_command_promise_.reset();
} else if (command_future_ != nullptr) {
command_future_->wait_for(std::chrono::milliseconds(1000));
+ hci_command_promise_.reset();
}
if (command_queue_.empty()) {
return ConnectionManagementCommandView::Create(AclCommandView::Create(
@@ -188,6 +199,19 @@
return command;
}
+ ConnectionManagementCommandView GetLastOutgoingCommand() {
+ if (command_queue_.empty()) {
+ // An empty packet will force a failure on |IsValid()| required by all packets before usage
+ return ConnectionManagementCommandView::Create(AclCommandView::Create(
+ CommandView::Create(PacketView<kLittleEndian>(std::make_shared<std::vector<uint8_t>>()))));
+ } else {
+ CommandView command_packet_view = GetLastCommand();
+ ConnectionManagementCommandView command =
+ ConnectionManagementCommandView::Create(AclCommandView::Create(command_packet_view));
+ return command;
+ }
+ }
+
void RegisterEventHandler(EventCode event_code, common::ContextualCallback<void(EventView)> event_handler) override {
registered_events_[event_code] = event_handler;
}
@@ -205,7 +229,7 @@
registered_le_events_.erase(subevent_code);
}
- void IncomingEvent(std::unique_ptr<EventBuilder> event_builder) {
+ void SendIncomingEvent(std::unique_ptr<EventBuilder> event_builder) {
auto packet = GetPacketView(std::move(event_builder));
EventView event = EventView::Create(packet);
ASSERT_TRUE(event.IsValid());
@@ -242,7 +266,7 @@
queue_end,
handle,
common::Passed(std::move(promise))));
- auto status = future.wait_for(kTimeout);
+ auto status = future.wait_for(2s);
ASSERT_EQ(status, std::future_status::ready);
}
@@ -291,7 +315,17 @@
GetHandler()->Post(common::BindOnce(&TestHciLayer::do_disconnect, common::Unretained(this), handle, reason));
}
+ std::unique_ptr<std::promise<void>> hci_command_promise_;
+
private:
+ void Notify() {
+ if (hci_command_promise_ != nullptr) {
+ std::promise<void>* prom = hci_command_promise_.release();
+ prom->set_value();
+ delete prom;
+ }
+ }
+
std::map<EventCode, common::ContextualCallback<void(EventView)>> registered_events_;
std::map<SubeventCode, common::ContextualCallback<void(LeMetaEventView)>> registered_le_events_;
std::list<common::ContextualOnceCallback<void(CommandCompleteView)>> command_complete_callbacks;
@@ -299,7 +333,6 @@
BidiQueue<AclView, AclBuilder> acl_queue_{3 /* TODO: Set queue depth */};
std::queue<std::unique_ptr<CommandBuilder>> command_queue_;
- std::unique_ptr<std::promise<void>> command_promise_;
std::unique_ptr<std::future<void>> command_future_;
void do_disconnect(uint16_t handle, ErrorCode reason) {
@@ -307,90 +340,122 @@
}
};
-class AclManagerNoCallbacksTest : public ::testing::Test {
+class MockConnectionCallback : public ConnectionCallbacks {
+ public:
+ void OnConnectSuccess(std::unique_ptr<ClassicAclConnection> connection) override {
+ // Convert to std::shared_ptr during push_back()
+ connections_.push_back(std::move(connection));
+ if (is_promise_set_) {
+ is_promise_set_ = false;
+ connection_promise_.set_value(connections_.back());
+ }
+ }
+ MOCK_METHOD(void, OnConnectFail, (Address, ErrorCode reason), (override));
+
+ MOCK_METHOD(void, HACK_OnEscoConnectRequest, (Address, ClassOfDevice), (override));
+ MOCK_METHOD(void, HACK_OnScoConnectRequest, (Address, ClassOfDevice), (override));
+
+ size_t NumberOfConnections() const {
+ return connections_.size();
+ }
+
+ private:
+ friend class AclManagerWithCallbacksTest;
+ friend class AclManagerNoCallbacksTest;
+
+ std::deque<std::shared_ptr<ClassicAclConnection>> connections_;
+ std::promise<std::shared_ptr<ClassicAclConnection>> connection_promise_;
+ bool is_promise_set_{false};
+};
+
+class MockLeConnectionCallbacks : public LeConnectionCallbacks {
+ public:
+ void OnLeConnectSuccess(AddressWithType address_with_type, std::unique_ptr<LeAclConnection> connection) override {
+ le_connections_.push_back(std::move(connection));
+ if (le_connection_promise_ != nullptr) {
+ std::promise<void>* prom = le_connection_promise_.release();
+ prom->set_value();
+ delete prom;
+ }
+ }
+ MOCK_METHOD(void, OnLeConnectFail, (AddressWithType, ErrorCode reason), (override));
+
+ std::deque<std::shared_ptr<LeAclConnection>> le_connections_;
+ std::unique_ptr<std::promise<void>> le_connection_promise_;
+};
+
+class AclManagerBaseTest : public ::testing::Test {
protected:
void SetUp() override {
+ common::InitFlags::SetAllForTesting();
test_hci_layer_ = new TestHciLayer; // Ownership is transferred to registry
+ ASSERT_TRUE(test_hci_layer_->hci_command_promise_ == nullptr) << "hci command is nullptr";
test_controller_ = new TestController;
fake_registry_.InjectTestModule(&HciLayer::Factory, test_hci_layer_);
fake_registry_.InjectTestModule(&Controller::Factory, test_controller_);
client_handler_ = fake_registry_.GetTestModuleHandler(&HciLayer::Factory);
ASSERT_NE(client_handler_, nullptr);
- test_hci_layer_->SetCommandFuture();
fake_registry_.Start<AclManager>(&thread_);
- acl_manager_ = static_cast<AclManager*>(fake_registry_.GetModuleUnderTest(&AclManager::Factory));
- Address::FromString("A1:A2:A3:A4:A5:A6", remote);
-
- hci::Address address;
- Address::FromString("D0:05:04:03:02:01", address);
- hci::AddressWithType address_with_type(address, hci::AddressType::RANDOM_DEVICE_ADDRESS);
- auto minimum_rotation_time = std::chrono::milliseconds(7 * 60 * 1000);
- auto maximum_rotation_time = std::chrono::milliseconds(15 * 60 * 1000);
- acl_manager_->SetPrivacyPolicyForInitiatorAddress(
- LeAddressManager::AddressPolicy::USE_STATIC_ADDRESS,
- address_with_type,
- minimum_rotation_time,
- maximum_rotation_time);
-
- auto set_random_address_packet = LeSetRandomAddressView::Create(
- LeAdvertisingCommandView::Create(test_hci_layer_->GetCommand(OpCode::LE_SET_RANDOM_ADDRESS)));
- ASSERT_TRUE(set_random_address_packet.IsValid());
- my_initiating_address =
- AddressWithType(set_random_address_packet.GetRandomAddress(), AddressType::RANDOM_DEVICE_ADDRESS);
- // Verify LE Set Random Address was sent during setup
- test_hci_layer_->GetLastCommand(OpCode::LE_SET_RANDOM_ADDRESS);
- test_hci_layer_->IncomingEvent(LeSetRandomAddressCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
+ ASSERT_TRUE(test_hci_layer_->hci_command_promise_ == nullptr) << "hci command is nullptr";
}
void TearDown() override {
- mock_connection_callbacks_.connections_.clear();
- mock_le_connection_callbacks_.le_connections_.clear();
-
fake_registry_.SynchronizeModuleHandler(&AclManager::Factory, std::chrono::milliseconds(20));
fake_registry_.StopAll();
}
- TestModuleRegistry fake_registry_;
+ void sync_client_handler() {
+ std::promise<void> promise;
+ auto future = promise.get_future();
+ client_handler_->Post(common::BindOnce(&std::promise<void>::set_value, common::Unretained(&promise)));
+ auto future_status = future.wait_for(std::chrono::seconds(1));
+ ASSERT_EQ(future_status, std::future_status::ready);
+ }
+
TestHciLayer* test_hci_layer_ = nullptr;
TestController* test_controller_ = nullptr;
+
+ TestModuleRegistry fake_registry_;
os::Thread& thread_ = fake_registry_.GetTestThread();
AclManager* acl_manager_ = nullptr;
os::Handler* client_handler_ = nullptr;
- Address remote;
- AddressWithType my_initiating_address;
+};
+
+class AclManagerNoCallbacksTest : public AclManagerBaseTest {
+ protected:
+ void SetUp() override {
+ AclManagerBaseTest::SetUp();
+ ASSERT_TRUE(test_hci_layer_->hci_command_promise_ == nullptr) << "hci command is nullptr";
+
+ acl_manager_ = static_cast<AclManager*>(fake_registry_.GetModuleUnderTest(&AclManager::Factory));
+
+ local_address_with_type_ = AddressWithType(
+ Address::FromString(kLocalRandomAddressString).value(), hci::AddressType::RANDOM_DEVICE_ADDRESS);
+
+ ASSERT_TRUE(test_hci_layer_->hci_command_promise_ == nullptr) << "hci command is nullptr";
+ auto future = test_hci_layer_->GetOutgoingCommandFuture();
+
+ acl_manager_->SetPrivacyPolicyForInitiatorAddress(
+ LeAddressManager::AddressPolicy::USE_STATIC_ADDRESS,
+ local_address_with_type_,
+ kMinimumRotationTime,
+ kMaximumRotationTime);
+
+ ASSERT_EQ(std::future_status::ready, future.wait_for(2s));
+ sync_client_handler();
+ ASSERT_TRUE(test_hci_layer_->hci_command_promise_ == nullptr) << "hci command is nullptr";
+ auto command = test_hci_layer_->GetLastOutgoingCommand();
+ ASSERT_TRUE(command.IsValid());
+ ASSERT_EQ(OpCode::LE_SET_RANDOM_ADDRESS, command.GetOpCode());
+ }
+
+ void TearDown() override {
+ AclManagerBaseTest::TearDown();
+ }
+
+ AddressWithType local_address_with_type_;
const bool use_connect_list_ = true; // gd currently only supports connect list
- void check_connection_promise_not_null() {
- ASSERT_TRUE(mock_connection_callbacks_.connection_promise_ == nullptr)
- << "Promises promises ... Only one at a time";
- }
-
- std::future<void> GetConnectionFuture() {
- check_connection_promise_not_null();
- mock_connection_callbacks_.connection_promise_ = std::make_unique<std::promise<void>>();
- return mock_connection_callbacks_.connection_promise_->get_future();
- }
-
- std::future<void> GetLeConnectionFuture() {
- check_connection_promise_not_null();
- mock_le_connection_callbacks_.le_connection_promise_ = std::make_unique<std::promise<void>>();
- return mock_le_connection_callbacks_.le_connection_promise_->get_future();
- }
-
- void check_connections_not_empty() {
- ASSERT_TRUE(!mock_connection_callbacks_.connections_.empty()) << "There are no classic ACL connections";
- }
-
- std::shared_ptr<ClassicAclConnection> GetLastConnection() {
- check_connections_not_empty();
- return mock_connection_callbacks_.connections_.back();
- }
-
- std::shared_ptr<LeAclConnection> GetLastLeConnection() {
- check_connections_not_empty();
- return mock_le_connection_callbacks_.le_connections_.back();
- }
-
void SendAclData(uint16_t handle, AclConnection::QueueUpEnd* queue_end) {
std::promise<void> promise;
auto future = promise.get_future();
@@ -405,51 +470,18 @@
queue_end,
handle,
common::Passed(std::move(promise))));
- auto status = future.wait_for(kTimeout);
+ auto status = future.wait_for(2s);
ASSERT_EQ(status, std::future_status::ready);
}
-
- class MockConnectionCallback : public ConnectionCallbacks {
- public:
- void OnConnectSuccess(std::unique_ptr<ClassicAclConnection> connection) override {
- // Convert to std::shared_ptr during push_back()
- connections_.push_back(std::move(connection));
- if (connection_promise_ != nullptr) {
- connection_promise_->set_value();
- connection_promise_.reset();
- }
- }
- MOCK_METHOD(void, OnConnectFail, (Address, ErrorCode reason), (override));
-
- MOCK_METHOD(void, HACK_OnEscoConnectRequest, (Address, ClassOfDevice), (override));
- MOCK_METHOD(void, HACK_OnScoConnectRequest, (Address, ClassOfDevice), (override));
-
- std::list<std::shared_ptr<ClassicAclConnection>> connections_;
- std::unique_ptr<std::promise<void>> connection_promise_;
- } mock_connection_callbacks_;
-
- class MockLeConnectionCallbacks : public LeConnectionCallbacks {
- public:
- void OnLeConnectSuccess(AddressWithType address_with_type, std::unique_ptr<LeAclConnection> connection) override {
- le_connections_.push_back(std::move(connection));
- if (le_connection_promise_ != nullptr) {
- le_connection_promise_->set_value();
- le_connection_promise_.reset();
- }
- }
- MOCK_METHOD(void, OnLeConnectFail, (AddressWithType, ErrorCode reason), (override));
-
- std::list<std::shared_ptr<LeAclConnection>> le_connections_;
- std::unique_ptr<std::promise<void>> le_connection_promise_;
- } mock_le_connection_callbacks_;
};
-class AclManagerTest : public AclManagerNoCallbacksTest {
+class AclManagerWithCallbacksTest : public AclManagerNoCallbacksTest {
protected:
void SetUp() override {
AclManagerNoCallbacksTest::SetUp();
acl_manager_->RegisterCallbacks(&mock_connection_callbacks_, client_handler_);
acl_manager_->RegisterLeCallbacks(&mock_le_connection_callbacks_, client_handler_);
+ ASSERT_TRUE(test_hci_layer_->hci_command_promise_ == nullptr) << "hci command is nullptr";
}
void TearDown() override {
@@ -468,16 +500,53 @@
acl_manager_->UnregisterCallbacks(&mock_connection_callbacks_, std::move(promise));
future.wait_for(2s);
}
+
+ mock_connection_callbacks_.connections_.clear();
+ mock_le_connection_callbacks_.le_connections_.clear();
+
AclManagerNoCallbacksTest::TearDown();
}
+
+ std::future<std::shared_ptr<ClassicAclConnection>> GetConnectionFuture() {
+ // Run on main thread
+ mock_connection_callbacks_.connection_promise_ = std::promise<std::shared_ptr<ClassicAclConnection>>();
+ mock_connection_callbacks_.is_promise_set_ = true;
+ return mock_connection_callbacks_.connection_promise_.get_future();
+ }
+
+ std::future<void> GetLeConnectionFuture() {
+ mock_le_connection_callbacks_.le_connection_promise_ = std::make_unique<std::promise<void>>();
+ return mock_le_connection_callbacks_.le_connection_promise_->get_future();
+ }
+
+ std::shared_ptr<ClassicAclConnection> GetLastConnection() {
+ return mock_connection_callbacks_.connections_.back();
+ }
+
+ size_t NumberOfConnections() {
+ return mock_connection_callbacks_.connections_.size();
+ }
+
+ std::shared_ptr<LeAclConnection> GetLastLeConnection() {
+ return mock_le_connection_callbacks_.le_connections_.back();
+ }
+
+ size_t NumberOfLeConnections() {
+ return mock_le_connection_callbacks_.le_connections_.size();
+ }
+
+ MockConnectionCallback mock_connection_callbacks_;
+ MockLeConnectionCallbacks mock_le_connection_callbacks_;
};
-class AclManagerWithConnectionTest : public AclManagerTest {
+class AclManagerWithConnectionTest : public AclManagerWithCallbacksTest {
protected:
void SetUp() override {
- AclManagerTest::SetUp();
+ AclManagerWithCallbacksTest::SetUp();
handle_ = 0x123;
+ Address::FromString("A1:A2:A3:A4:A5:A6", remote);
+
acl_manager_->CreateConnection(remote);
// Wait for the connection request
@@ -489,10 +558,10 @@
EXPECT_CALL(mock_connection_management_callbacks_, OnRoleChange(hci::ErrorCode::SUCCESS, Role::CENTRAL));
auto first_connection = GetConnectionFuture();
- test_hci_layer_->IncomingEvent(
+ test_hci_layer_->SendIncomingEvent(
ConnectionCompleteBuilder::Create(ErrorCode::SUCCESS, handle_, remote, LinkType::ACL, Enable::DISABLED));
- auto first_connection_status = first_connection.wait_for(kTimeout);
+ auto first_connection_status = first_connection.wait_for(2s);
ASSERT_EQ(first_connection_status, std::future_status::ready);
connection_ = GetLastConnection();
@@ -505,15 +574,8 @@
fake_registry_.StopAll();
}
- void sync_client_handler() {
- std::promise<void> promise;
- auto future = promise.get_future();
- client_handler_->Post(common::BindOnce(&std::promise<void>::set_value, common::Unretained(&promise)));
- auto future_status = future.wait_for(std::chrono::seconds(1));
- ASSERT_EQ(future_status, std::future_status::ready);
- }
-
uint16_t handle_;
+ Address remote;
std::shared_ptr<ClassicAclConnection> connection_;
class MockConnectionManagementCallbacks : public ConnectionManagementCallbacks {
@@ -572,19 +634,20 @@
} mock_connection_management_callbacks_;
};
-TEST_F(AclManagerTest, startup_teardown) {}
+TEST_F(AclManagerWithCallbacksTest, startup_teardown) {}
-class AclManagerWithLeConnectionTest : public AclManagerTest {
+class AclManagerWithLeConnectionTest : public AclManagerWithCallbacksTest {
protected:
void SetUp() override {
- AclManagerTest::SetUp();
+ AclManagerWithCallbacksTest::SetUp();
- remote_with_type_ = AddressWithType(remote, AddressType::PUBLIC_DEVICE_ADDRESS);
- test_hci_layer_->SetCommandFuture();
+ Address remote_public_address = Address::FromString(kRemotePublicDeviceStringA).value();
+ remote_with_type_ = AddressWithType(remote_public_address, AddressType::PUBLIC_DEVICE_ADDRESS);
+ ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
acl_manager_->CreateLeConnection(remote_with_type_, true);
test_hci_layer_->GetCommand(OpCode::LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST);
- test_hci_layer_->IncomingEvent(LeAddDeviceToFilterAcceptListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
- test_hci_layer_->SetCommandFuture();
+ test_hci_layer_->SendIncomingEvent(LeAddDeviceToFilterAcceptListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
+ ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
auto packet = test_hci_layer_->GetCommand(OpCode::LE_CREATE_CONNECTION);
auto le_connection_management_command_view =
LeConnectionManagementCommandView::Create(AclCommandView::Create(packet));
@@ -594,11 +657,11 @@
ASSERT_EQ(command_view.GetPeerAddress(), empty_address_with_type.GetAddress());
ASSERT_EQ(command_view.GetPeerAddressType(), empty_address_with_type.GetAddressType());
} else {
- ASSERT_EQ(command_view.GetPeerAddress(), remote);
+ ASSERT_EQ(command_view.GetPeerAddress(), remote_public_address);
ASSERT_EQ(command_view.GetPeerAddressType(), AddressType::PUBLIC_DEVICE_ADDRESS);
}
- test_hci_layer_->IncomingEvent(LeCreateConnectionStatusBuilder::Create(ErrorCode::SUCCESS, 0x01));
+ test_hci_layer_->SendIncomingEvent(LeCreateConnectionStatusBuilder::Create(ErrorCode::SUCCESS, 0x01));
auto first_connection = GetLeConnectionFuture();
@@ -607,17 +670,18 @@
handle_,
Role::PERIPHERAL,
AddressType::PUBLIC_DEVICE_ADDRESS,
- remote,
+ remote_public_address,
0x0100,
0x0010,
0x0C80,
ClockAccuracy::PPM_30));
- test_hci_layer_->SetCommandFuture();
+ ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
test_hci_layer_->GetCommand(OpCode::LE_REMOVE_DEVICE_FROM_FILTER_ACCEPT_LIST);
- test_hci_layer_->IncomingEvent(LeRemoveDeviceFromFilterAcceptListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
+ test_hci_layer_->SendIncomingEvent(
+ LeRemoveDeviceFromFilterAcceptListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
- auto first_connection_status = first_connection.wait_for(kTimeout);
+ auto first_connection_status = first_connection.wait_for(2s);
ASSERT_EQ(first_connection_status, std::future_status::ready);
connection_ = GetLastLeConnection();
@@ -661,7 +725,7 @@
} mock_le_connection_management_callbacks_;
};
-class AclManagerWithResolvableAddressTest : public AclManagerNoCallbacksTest {
+class AclManagerWithResolvableAddressTest : public AclManagerWithCallbacksTest {
protected:
void SetUp() override {
test_hci_layer_ = new TestHciLayer; // Ownership is transferred to registry
@@ -670,11 +734,9 @@
fake_registry_.InjectTestModule(&Controller::Factory, test_controller_);
client_handler_ = fake_registry_.GetTestModuleHandler(&HciLayer::Factory);
ASSERT_NE(client_handler_, nullptr);
- test_hci_layer_->SetCommandFuture();
+ ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
fake_registry_.Start<AclManager>(&thread_);
acl_manager_ = static_cast<AclManager*>(fake_registry_.GetModuleUnderTest(&AclManager::Factory));
- Address::FromString("A1:A2:A3:A4:A5:A6", remote);
-
hci::Address address;
Address::FromString("D0:05:04:03:02:01", address);
hci::AddressWithType address_with_type(address, hci::AddressType::RANDOM_DEVICE_ADDRESS);
@@ -689,25 +751,17 @@
maximum_rotation_time);
test_hci_layer_->GetLastCommand(OpCode::LE_SET_RANDOM_ADDRESS);
- test_hci_layer_->IncomingEvent(LeSetRandomAddressCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
+ test_hci_layer_->SendIncomingEvent(LeSetRandomAddressCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
}
};
-class AclManagerLifeCycleTest : public AclManagerNoCallbacksTest {
- protected:
- void SetUp() override {
- AclManagerNoCallbacksTest::SetUp();
- acl_manager_->RegisterCallbacks(&mock_connection_callbacks_, client_handler_);
- acl_manager_->RegisterLeCallbacks(&mock_le_connection_callbacks_, client_handler_);
- }
-
- AddressWithType remote_with_type_;
- uint16_t handle_{0x123};
-};
-
-TEST_F(AclManagerLifeCycleTest, unregister_classic_before_connection_request) {
+TEST_F(AclManagerNoCallbacksTest, unregister_classic_before_connection_request) {
ClassOfDevice class_of_device;
+ MockConnectionCallback mock_connection_callbacks_;
+
+ acl_manager_->RegisterCallbacks(&mock_connection_callbacks_, client_handler_);
+
// Unregister callbacks before receiving connection request
auto promise = std::promise<void>();
auto future = promise.get_future();
@@ -715,103 +769,126 @@
future.get();
// Inject peer sending connection request
- auto connection_future = GetConnectionFuture();
- test_hci_layer_->IncomingEvent(
- ConnectionRequestBuilder::Create(remote, class_of_device, ConnectionRequestLinkType::ACL));
- auto connection_future_status = connection_future.wait_for(kTimeout);
- ASSERT_NE(connection_future_status, std::future_status::ready);
+ test_hci_layer_->SendIncomingEvent(ConnectionRequestBuilder::Create(
+ local_address_with_type_.GetAddress(), class_of_device, ConnectionRequestLinkType::ACL));
+ sync_client_handler();
- test_hci_layer_->GetLastCommand(OpCode::REJECT_CONNECTION_REQUEST);
+ // There should be no connections
+ ASSERT_EQ(0UL, mock_connection_callbacks_.NumberOfConnections());
+
+ auto command = test_hci_layer_->GetLastOutgoingCommand();
+ ASSERT_TRUE(command.IsValid());
+ ASSERT_EQ(OpCode::REJECT_CONNECTION_REQUEST, command.GetOpCode());
}
-TEST_F(AclManagerTest, two_remote_connection_requests_ABAB) {
- struct {
- Address address;
- ClassOfDevice class_of_device;
- const uint16_t handle;
- } remote[2] = {
- {
- .address = {},
- .class_of_device = {},
- .handle = 123,
- },
- {.address = {}, .class_of_device = {}, .handle = 456},
- };
- Address::FromString("A1:A2:A3:A4:A5:A6", remote[0].address);
- Address::FromString("B1:B2:B3:B4:B5:B6", remote[1].address);
-
- test_hci_layer_->SetCommandFuture();
- test_hci_layer_->IncomingEvent(
- ConnectionRequestBuilder::Create(remote[0].address, remote[0].class_of_device, ConnectionRequestLinkType::ACL));
- test_hci_layer_->GetLastCommand(OpCode::ACCEPT_CONNECTION_REQUEST);
-
- test_hci_layer_->SetCommandFuture();
- test_hci_layer_->IncomingEvent(
- ConnectionRequestBuilder::Create(remote[1].address, remote[1].class_of_device, ConnectionRequestLinkType::ACL));
- test_hci_layer_->GetLastCommand(OpCode::ACCEPT_CONNECTION_REQUEST);
+TEST_F(AclManagerWithCallbacksTest, two_remote_connection_requests_ABAB) {
+ Address::FromString(kRemotePublicDeviceStringA, remote_device[0].address);
+ Address::FromString(kRemotePublicDeviceStringB, remote_device[1].address);
{
- auto first_connection = GetConnectionFuture();
- test_hci_layer_->IncomingEvent(ConnectionCompleteBuilder::Create(
- ErrorCode::SUCCESS, remote[0].handle, remote[0].address, LinkType::ACL, Enable::DISABLED));
- auto first_connection_status = first_connection.wait_for(kTimeout);
- ASSERT_EQ(first_connection_status, std::future_status::ready);
+ // Device A sends connection request
+ auto future = test_hci_layer_->GetOutgoingCommandFuture();
+ test_hci_layer_->SendIncomingEvent(ConnectionRequestBuilder::Create(
+ remote_device[0].address, remote_device[0].class_of_device, ConnectionRequestLinkType::ACL));
+ sync_client_handler();
+ // Verify we accept this connection
+ ASSERT_EQ(std::future_status::ready, future.wait_for(2s));
+ auto command = test_hci_layer_->GetLastOutgoingCommand();
+ ASSERT_TRUE(command.IsValid());
+ ASSERT_EQ(OpCode::ACCEPT_CONNECTION_REQUEST, command.GetOpCode());
}
- ASSERT_EQ(GetLastConnection()->GetAddress(), remote[0].address);
{
- auto first_connection = GetConnectionFuture();
- test_hci_layer_->IncomingEvent(ConnectionCompleteBuilder::Create(
- ErrorCode::SUCCESS, remote[1].handle, remote[1].address, LinkType::ACL, Enable::DISABLED));
- auto first_connection_status = first_connection.wait_for(2s);
- ASSERT_EQ(first_connection_status, std::future_status::ready);
+ // Device B sends connection request
+ auto future = test_hci_layer_->GetOutgoingCommandFuture();
+ test_hci_layer_->SendIncomingEvent(ConnectionRequestBuilder::Create(
+ remote_device[1].address, remote_device[1].class_of_device, ConnectionRequestLinkType::ACL));
+ sync_client_handler();
+ // Verify we accept this connection
+ ASSERT_EQ(std::future_status::ready, future.wait_for(2s));
+ auto command = test_hci_layer_->GetLastOutgoingCommand();
+ ASSERT_TRUE(command.IsValid());
+ ASSERT_EQ(OpCode::ACCEPT_CONNECTION_REQUEST, command.GetOpCode());
}
- ASSERT_EQ(GetLastConnection()->GetAddress(), remote[1].address);
+
+ ASSERT_EQ(0UL, NumberOfConnections());
+
+ {
+ // Device A completes first connection
+ auto future = GetConnectionFuture();
+ test_hci_layer_->SendIncomingEvent(ConnectionCompleteBuilder::Create(
+ ErrorCode::SUCCESS, remote_device[0].handle, remote_device[0].address, LinkType::ACL, Enable::DISABLED));
+ ASSERT_EQ(std::future_status::ready, future.wait_for(2s)) << "Timeout waiting for first connection complete";
+ ASSERT_EQ(1UL, NumberOfConnections());
+ auto connection = future.get();
+ ASSERT_EQ(connection->GetAddress(), remote_device[0].address) << "First connection remote address mismatch";
+ }
+
+ {
+ // Device B completes second connection
+ auto future = GetConnectionFuture();
+ test_hci_layer_->SendIncomingEvent(ConnectionCompleteBuilder::Create(
+ ErrorCode::SUCCESS, remote_device[1].handle, remote_device[1].address, LinkType::ACL, Enable::DISABLED));
+ ASSERT_EQ(std::future_status::ready, future.wait_for(2s)) << "Timeout waiting for second connection complete";
+ ASSERT_EQ(2UL, NumberOfConnections());
+ auto connection = future.get();
+ ASSERT_EQ(connection->GetAddress(), remote_device[1].address) << "Second connection remote address mismatch";
+ }
}
-TEST_F(AclManagerTest, two_remote_connection_requests_ABBA) {
- struct {
- Address address;
- ClassOfDevice class_of_device;
- const uint16_t handle;
- } remote[2] = {
- {
- .address = {},
- .class_of_device = {},
- .handle = 123,
- },
- {.address = {}, .class_of_device = {}, .handle = 456},
- };
- Address::FromString("A1:A2:A3:A4:A5:A6", remote[0].address);
- Address::FromString("B1:B2:B3:B4:B5:B6", remote[1].address);
-
- test_hci_layer_->SetCommandFuture();
- test_hci_layer_->IncomingEvent(
- ConnectionRequestBuilder::Create(remote[0].address, remote[0].class_of_device, ConnectionRequestLinkType::ACL));
- test_hci_layer_->GetLastCommand(OpCode::ACCEPT_CONNECTION_REQUEST);
-
- test_hci_layer_->SetCommandFuture();
- test_hci_layer_->IncomingEvent(
- ConnectionRequestBuilder::Create(remote[1].address, remote[1].class_of_device, ConnectionRequestLinkType::ACL));
- test_hci_layer_->GetLastCommand(OpCode::ACCEPT_CONNECTION_REQUEST);
+TEST_F(AclManagerWithCallbacksTest, two_remote_connection_requests_ABBA) {
+ Address::FromString(kRemotePublicDeviceStringA, remote_device[0].address);
+ Address::FromString(kRemotePublicDeviceStringB, remote_device[1].address);
{
- auto first_connection = GetConnectionFuture();
- test_hci_layer_->IncomingEvent(ConnectionCompleteBuilder::Create(
- ErrorCode::SUCCESS, remote[1].handle, remote[1].address, LinkType::ACL, Enable::DISABLED));
- auto first_connection_status = first_connection.wait_for(2s);
- ASSERT_EQ(first_connection_status, std::future_status::ready);
+ // Device A sends connection request
+ auto future = test_hci_layer_->GetOutgoingCommandFuture();
+ test_hci_layer_->SendIncomingEvent(ConnectionRequestBuilder::Create(
+ remote_device[0].address, remote_device[0].class_of_device, ConnectionRequestLinkType::ACL));
+ sync_client_handler();
+ // Verify we accept this connection
+ ASSERT_EQ(std::future_status::ready, future.wait_for(2s));
+ auto command = test_hci_layer_->GetLastOutgoingCommand();
+ ASSERT_TRUE(command.IsValid());
+ ASSERT_EQ(OpCode::ACCEPT_CONNECTION_REQUEST, command.GetOpCode());
}
- ASSERT_EQ(GetLastConnection()->GetAddress(), remote[1].address);
{
- auto first_connection = GetConnectionFuture();
- test_hci_layer_->IncomingEvent(ConnectionCompleteBuilder::Create(
- ErrorCode::SUCCESS, remote[0].handle, remote[0].address, LinkType::ACL, Enable::DISABLED));
- auto first_connection_status = first_connection.wait_for(kTimeout);
- ASSERT_EQ(first_connection_status, std::future_status::ready);
+ // Device B sends connection request
+ auto future = test_hci_layer_->GetOutgoingCommandFuture();
+ test_hci_layer_->SendIncomingEvent(ConnectionRequestBuilder::Create(
+ remote_device[1].address, remote_device[1].class_of_device, ConnectionRequestLinkType::ACL));
+ sync_client_handler();
+ // Verify we accept this connection
+ ASSERT_EQ(std::future_status::ready, future.wait_for(2s));
+ auto command = test_hci_layer_->GetLastOutgoingCommand();
+ ASSERT_TRUE(command.IsValid());
+ ASSERT_EQ(OpCode::ACCEPT_CONNECTION_REQUEST, command.GetOpCode());
}
- ASSERT_EQ(GetLastConnection()->GetAddress(), remote[0].address);
+
+ ASSERT_EQ(0UL, NumberOfConnections());
+
+ {
+ // Device B completes first connection
+ auto future = GetConnectionFuture();
+ test_hci_layer_->SendIncomingEvent(ConnectionCompleteBuilder::Create(
+ ErrorCode::SUCCESS, remote_device[1].handle, remote_device[1].address, LinkType::ACL, Enable::DISABLED));
+ ASSERT_EQ(std::future_status::ready, future.wait_for(2s)) << "Timeout waiting for first connection complete";
+ ASSERT_EQ(1UL, NumberOfConnections());
+ auto connection = future.get();
+ ASSERT_EQ(connection->GetAddress(), remote_device[1].address) << "First connection remote address mismatch";
+ }
+
+ {
+ // Device A completes second connection
+ auto future = GetConnectionFuture();
+ test_hci_layer_->SendIncomingEvent(ConnectionCompleteBuilder::Create(
+ ErrorCode::SUCCESS, remote_device[0].handle, remote_device[0].address, LinkType::ACL, Enable::DISABLED));
+ ASSERT_EQ(std::future_status::ready, future.wait_for(2s)) << "Timeout waiting for second connection complete";
+ ASSERT_EQ(2UL, NumberOfConnections());
+ auto connection = future.get();
+ ASSERT_EQ(connection->GetAddress(), remote_device[0].address) << "Second connection remote address mismatch";
+ }
}
} // namespace
diff --git a/system/gd/hci/address.cc b/system/gd/hci/address.cc
index 3dc013f..7409418 100644
--- a/system/gd/hci/address.cc
+++ b/system/gd/hci/address.cc
@@ -42,10 +42,15 @@
std::copy(l.begin(), std::min(l.begin() + kLength, l.end()), data());
}
-std::string Address::ToString() const {
+std::string Address::_ToMaskedColonSepHexString(int bytes_to_mask) const {
std::stringstream ss;
+ int count = 0;
for (auto it = address.rbegin(); it != address.rend(); it++) {
- ss << std::nouppercase << std::hex << std::setw(2) << std::setfill('0') << +*it;
+ if (count++ < bytes_to_mask) {
+ ss << "xx";
+ } else {
+ ss << std::nouppercase << std::hex << std::setw(2) << std::setfill('0') << +*it;
+ }
if (std::next(it) != address.rend()) {
ss << ':';
}
@@ -53,6 +58,22 @@
return ss.str();
}
+std::string Address::ToString() const {
+ return _ToMaskedColonSepHexString(0);
+}
+
+std::string Address::ToColonSepHexString() const {
+ return _ToMaskedColonSepHexString(0);
+}
+
+std::string Address::ToStringForLogging() const {
+ return _ToMaskedColonSepHexString(0);
+}
+
+std::string Address::ToRedactedStringForLogging() const {
+ return _ToMaskedColonSepHexString(4);
+}
+
std::string Address::ToLegacyConfigString() const {
return ToString();
}
diff --git a/system/gd/hci/address.h b/system/gd/hci/address.h
index c5e43d4..2c1dfff 100644
--- a/system/gd/hci/address.h
+++ b/system/gd/hci/address.h
@@ -25,13 +25,16 @@
#include <ostream>
#include <string>
+#include "common/interfaces/ILoggable.h"
#include "packet/custom_field_fixed_size_interface.h"
#include "storage/serializable.h"
namespace bluetooth {
namespace hci {
-class Address final : public packet::CustomFieldFixedSizeInterface<Address>, public storage::Serializable<Address> {
+class Address final : public packet::CustomFieldFixedSizeInterface<Address>,
+ public storage::Serializable<Address>,
+ public bluetooth::common::IRedactableLoggable {
public:
static constexpr size_t kLength = 6;
@@ -51,6 +54,10 @@
// storage::Serializable methods
std::string ToString() const override;
+ std::string ToColonSepHexString() const;
+ std::string ToStringForLogging() const override;
+ std::string ToRedactedStringForLogging() const override;
+
static std::optional<Address> FromString(const std::string& from);
std::string ToLegacyConfigString() const override;
static std::optional<Address> FromLegacyConfigString(const std::string& str);
@@ -91,8 +98,12 @@
static const Address kEmpty; // 00:00:00:00:00:00
static const Address kAny; // FF:FF:FF:FF:FF:FF
+ private:
+ std::string _ToMaskedColonSepHexString(int bytes_to_mask) const;
};
+// TODO: to fine-tune this.
+// we need an interface between the logger and ILoggable
inline std::ostream& operator<<(std::ostream& os, const Address& a) {
os << a.ToString();
return os;
diff --git a/system/gd/hci/address_unittest.cc b/system/gd/hci/address_unittest.cc
index ae2c2e7..a675682 100644
--- a/system/gd/hci/address_unittest.cc
+++ b/system/gd/hci/address_unittest.cc
@@ -16,12 +16,14 @@
*
******************************************************************************/
-#include <unordered_map>
+#include "hci/address.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
-#include "hci/address.h"
+#include <cstdint>
+#include <string>
+#include <unordered_map>
using bluetooth::hci::Address;
@@ -233,3 +235,14 @@
struct std::hash<Address> hasher;
ASSERT_NE(hasher(Address::kEmpty), hasher(Address::kAny));
}
+
+TEST(AddressTest, ToStringForLoggingTestOutputUnderDebuggablePropAndInitFlag) {
+ Address addr{{0xab, 0x55, 0x44, 0x33, 0x22, 0x11}};
+ const std::string redacted_loggable_str = "xx:xx:xx:xx:55:ab";
+ const std::string loggable_str = "11:22:33:44:55:ab";
+
+ std::string ret1 = addr.ToStringForLogging();
+ ASSERT_STREQ(ret1.c_str(), loggable_str.c_str());
+ std::string ret2 = addr.ToRedactedStringForLogging();
+ ASSERT_STREQ(ret2.c_str(), redacted_loggable_str.c_str());
+}
diff --git a/system/gd/hci/address_with_type.h b/system/gd/hci/address_with_type.h
index ec4d40c..75a0b3c 100644
--- a/system/gd/hci/address_with_type.h
+++ b/system/gd/hci/address_with_type.h
@@ -22,6 +22,7 @@
#include <string>
#include <utility>
+#include "common/interfaces/ILoggable.h"
#include "crypto_toolbox/crypto_toolbox.h"
#include "hci/address.h"
#include "hci/hci_packets.h"
@@ -29,7 +30,7 @@
namespace bluetooth {
namespace hci {
-class AddressWithType final {
+class AddressWithType final : public bluetooth::common::IRedactableLoggable {
public:
AddressWithType(Address address, AddressType address_type)
: address_(std::move(address)), address_type_(address_type) {}
@@ -119,6 +120,14 @@
return ss.str();
}
+ std::string ToStringForLogging() const override {
+ return address_.ToStringForLogging() + "[" + AddressTypeText(address_type_) + "]";
+ }
+
+ std::string ToRedactedStringForLogging() const override {
+ return address_.ToStringForLogging() + "[" + AddressTypeText(address_type_) + "]";
+ }
+
private:
Address address_;
AddressType address_type_;
diff --git a/system/gd/hci/controller.cc b/system/gd/hci/controller.cc
index 3c8b69d..9dac2b6 100644
--- a/system/gd/hci/controller.cc
+++ b/system/gd/hci/controller.cc
@@ -870,27 +870,27 @@
CompletedAclPacketsCallback acl_credits_callback_{};
CompletedAclPacketsCallback acl_monitor_credits_callback_{};
- LocalVersionInformation local_version_information_;
- std::array<uint8_t, 64> local_supported_commands_;
- std::vector<uint64_t> extended_lmp_features_array_;
- uint16_t acl_buffer_length_ = 0;
- uint16_t acl_buffers_ = 0;
- uint8_t sco_buffer_length_ = 0;
- uint16_t sco_buffers_ = 0;
- Address mac_address_;
- std::string local_name_;
- LeBufferSize le_buffer_size_;
- LeBufferSize iso_buffer_size_;
- uint64_t le_local_supported_features_;
- uint64_t le_supported_states_;
- uint8_t le_connect_list_size_;
- uint8_t le_resolving_list_size_;
- LeMaximumDataLength le_maximum_data_length_;
- uint16_t le_maximum_advertising_data_length_;
- uint16_t le_suggested_default_data_length_;
- uint8_t le_number_supported_advertising_sets_;
- uint8_t le_periodic_advertiser_list_size_;
- VendorCapabilities vendor_capabilities_;
+ LocalVersionInformation local_version_information_{};
+ std::array<uint8_t, 64> local_supported_commands_{};
+ std::vector<uint64_t> extended_lmp_features_array_{};
+ uint16_t acl_buffer_length_{};
+ uint16_t acl_buffers_{};
+ uint8_t sco_buffer_length_{};
+ uint16_t sco_buffers_{};
+ Address mac_address_{};
+ std::string local_name_{};
+ LeBufferSize le_buffer_size_{};
+ LeBufferSize iso_buffer_size_{};
+ uint64_t le_local_supported_features_{};
+ uint64_t le_supported_states_{};
+ uint8_t le_connect_list_size_{};
+ uint8_t le_resolving_list_size_{};
+ LeMaximumDataLength le_maximum_data_length_{};
+ uint16_t le_maximum_advertising_data_length_{};
+ uint16_t le_suggested_default_data_length_{};
+ uint8_t le_number_supported_advertising_sets_{};
+ uint8_t le_periodic_advertiser_list_size_{};
+ VendorCapabilities vendor_capabilities_{};
}; // namespace hci
Controller::Controller() : impl_(std::make_unique<impl>(*this)) {}
diff --git a/system/gd/hci/controller_test.cc b/system/gd/hci/controller_test.cc
index dacdef7..6353482 100644
--- a/system/gd/hci/controller_test.cc
+++ b/system/gd/hci/controller_test.cc
@@ -54,7 +54,6 @@
uint16_t feature_spec_version = 55;
constexpr char title[] = "hci_controller_test";
-
PacketView<kLittleEndian> GetPacketView(std::unique_ptr<packet::BasePacketBuilder> packet) {
auto bytes = std::make_shared<std::vector<uint8_t>>();
BitInserter i(*bytes);
@@ -65,6 +64,8 @@
} // namespace
+namespace {
+
class TestHciLayer : public HciLayer {
public:
void EnqueueCommand(
@@ -281,9 +282,11 @@
std::condition_variable not_empty_;
};
+} // namespace
class ControllerTest : public ::testing::Test {
protected:
void SetUp() override {
+ feature_spec_version = feature_spec_version_;
bluetooth::common::InitFlags::SetAllForTesting();
test_hci_layer_ = new TestHciLayer;
fake_registry_.InjectTestModule(&HciLayer::Factory, test_hci_layer_);
@@ -301,6 +304,31 @@
os::Thread& thread_ = fake_registry_.GetTestThread();
Controller* controller_ = nullptr;
os::Handler* client_handler_ = nullptr;
+ uint16_t feature_spec_version_ = 98;
+};
+
+class Controller055Test : public ControllerTest {
+ protected:
+ void SetUp() override {
+ feature_spec_version_ = 55;
+ ControllerTest::SetUp();
+ }
+};
+
+class Controller095Test : public ControllerTest {
+ protected:
+ void SetUp() override {
+ feature_spec_version_ = 95;
+ ControllerTest::SetUp();
+ }
+};
+
+class Controller096Test : public ControllerTest {
+ protected:
+ void SetUp() override {
+ feature_spec_version_ = 96;
+ ControllerTest::SetUp();
+ }
};
TEST_F(ControllerTest, startup_teardown) {}
@@ -407,28 +435,25 @@
ASSERT_FALSE(controller_->IsSupported(OpCode::LE_SET_PERIODIC_ADVERTISING_PARAM));
}
-TEST_F(ControllerTest, feature_spec_version_055_test) {
+TEST_F(Controller055Test, feature_spec_version_055_test) {
ASSERT_EQ(controller_->GetVendorCapabilities().version_supported_, 55);
ASSERT_TRUE(controller_->IsSupported(OpCode::LE_MULTI_ADVT));
ASSERT_FALSE(controller_->IsSupported(OpCode::CONTROLLER_DEBUG_INFO));
ASSERT_FALSE(controller_->IsSupported(OpCode::CONTROLLER_A2DP_OPCODE));
- feature_spec_version = 95;
}
-TEST_F(ControllerTest, feature_spec_version_095_test) {
+TEST_F(Controller095Test, feature_spec_version_095_test) {
ASSERT_EQ(controller_->GetVendorCapabilities().version_supported_, 95);
ASSERT_TRUE(controller_->IsSupported(OpCode::LE_MULTI_ADVT));
ASSERT_FALSE(controller_->IsSupported(OpCode::CONTROLLER_DEBUG_INFO));
ASSERT_FALSE(controller_->IsSupported(OpCode::CONTROLLER_A2DP_OPCODE));
- feature_spec_version = 96;
}
-TEST_F(ControllerTest, feature_spec_version_096_test) {
+TEST_F(Controller096Test, feature_spec_version_096_test) {
ASSERT_EQ(controller_->GetVendorCapabilities().version_supported_, 96);
ASSERT_TRUE(controller_->IsSupported(OpCode::LE_MULTI_ADVT));
ASSERT_FALSE(controller_->IsSupported(OpCode::CONTROLLER_DEBUG_INFO));
ASSERT_FALSE(controller_->IsSupported(OpCode::CONTROLLER_A2DP_OPCODE));
- feature_spec_version = 98;
}
TEST_F(ControllerTest, feature_spec_version_098_test) {
@@ -436,7 +461,6 @@
ASSERT_TRUE(controller_->IsSupported(OpCode::LE_MULTI_ADVT));
ASSERT_FALSE(controller_->IsSupported(OpCode::CONTROLLER_DEBUG_INFO));
ASSERT_TRUE(controller_->IsSupported(OpCode::CONTROLLER_A2DP_OPCODE));
- feature_spec_version = 55;
}
std::promise<void> credits1_set;
diff --git a/system/gd/hci/hci_layer_fake.cc b/system/gd/hci/hci_layer_fake.cc
index 65ef988..3000c56 100644
--- a/system/gd/hci/hci_layer_fake.cc
+++ b/system/gd/hci/hci_layer_fake.cc
@@ -25,6 +25,8 @@
namespace bluetooth {
namespace hci {
+using common::BidiQueue;
+using common::BidiQueueEnd;
using packet::kLittleEndian;
using packet::PacketView;
using packet::RawBuilder;
@@ -37,6 +39,22 @@
return packet::PacketView<packet::kLittleEndian>(bytes);
}
+std::unique_ptr<BasePacketBuilder> NextPayload(uint16_t handle) {
+ static uint32_t packet_number = 1;
+ auto payload = std::make_unique<RawBuilder>();
+ payload->AddOctets2(6); // L2CAP PDU size
+ payload->AddOctets2(2); // L2CAP CID
+ payload->AddOctets2(handle);
+ payload->AddOctets4(packet_number++);
+ return std::move(payload);
+}
+
+static std::unique_ptr<AclBuilder> NextAclPacket(uint16_t handle) {
+ PacketBoundaryFlag packet_boundary_flag = PacketBoundaryFlag::FIRST_AUTOMATICALLY_FLUSHABLE;
+ BroadcastFlag broadcast_flag = BroadcastFlag::POINT_TO_POINT;
+ return AclBuilder::Create(handle, packet_boundary_flag, broadcast_flag, NextPayload(handle));
+}
+
void TestHciLayer::EnqueueCommand(
std::unique_ptr<CommandBuilder> command, common::ContextualOnceCallback<void(CommandStatusView)> on_status) {
std::lock_guard<std::mutex> lock(mutex_);
@@ -150,10 +168,59 @@
ASSERT_TRUE(empty_command_view_.IsValid());
}
+void TestHciLayer::IncomingAclData(uint16_t handle) {
+ os::Handler* hci_handler = GetHandler();
+ auto* queue_end = acl_queue_.GetDownEnd();
+ std::promise<void> promise;
+ auto future = promise.get_future();
+ queue_end->RegisterEnqueue(
+ hci_handler,
+ common::Bind(
+ [](decltype(queue_end) queue_end, uint16_t handle, std::promise<void> promise) {
+ auto packet = GetPacketView(NextAclPacket(handle));
+ AclView acl2 = AclView::Create(packet);
+ queue_end->UnregisterEnqueue();
+ promise.set_value();
+ return std::make_unique<AclView>(acl2);
+ },
+ queue_end,
+ handle,
+ common::Passed(std::move(promise))));
+ auto status = future.wait_for(std::chrono::milliseconds(1000));
+ ASSERT_EQ(status, std::future_status::ready);
+}
+
+void TestHciLayer::AssertNoOutgoingAclData() {
+ auto queue_end = acl_queue_.GetDownEnd();
+ EXPECT_EQ(queue_end->TryDequeue(), nullptr);
+}
+
+PacketView<kLittleEndian> TestHciLayer::OutgoingAclData() {
+ auto queue_end = acl_queue_.GetDownEnd();
+ std::unique_ptr<AclBuilder> received;
+ do {
+ received = queue_end->TryDequeue();
+ } while (received == nullptr);
+
+ return GetPacketView(std::move(received));
+}
+
+BidiQueueEnd<AclBuilder, AclView>* TestHciLayer::GetAclQueueEnd() {
+ return acl_queue_.GetUpEnd();
+}
+
+void TestHciLayer::Disconnect(uint16_t handle, ErrorCode reason) {
+ GetHandler()->Post(
+ common::BindOnce(&TestHciLayer::do_disconnect, common::Unretained(this), handle, reason));
+}
+
+void TestHciLayer::do_disconnect(uint16_t handle, ErrorCode reason) {
+ HciLayer::Disconnect(handle, reason);
+}
+
void TestHciLayer::ListDependencies(ModuleList* list) const {}
void TestHciLayer::Start() {
std::lock_guard<std::mutex> lock(mutex_);
-
InitEmptyCommand();
}
void TestHciLayer::Stop() {}
diff --git a/system/gd/hci/hci_layer_fake.h b/system/gd/hci/hci_layer_fake.h
index a944286..a2c3ea1 100644
--- a/system/gd/hci/hci_layer_fake.h
+++ b/system/gd/hci/hci_layer_fake.h
@@ -25,10 +25,10 @@
namespace bluetooth {
namespace hci {
-using packet::kLittleEndian;
-using packet::PacketView;
+packet::PacketView<packet::kLittleEndian> GetPacketView(
+ std::unique_ptr<packet::BasePacketBuilder> packet);
-PacketView<kLittleEndian> GetPacketView(std::unique_ptr<packet::BasePacketBuilder> packet);
+std::unique_ptr<BasePacketBuilder> NextPayload(uint16_t handle);
class TestHciLayer : public HciLayer {
public:
@@ -59,6 +59,16 @@
void CommandStatusCallback(EventView event);
+ void IncomingAclData(uint16_t handle);
+
+ void AssertNoOutgoingAclData();
+
+ packet::PacketView<packet::kLittleEndian> OutgoingAclData();
+
+ common::BidiQueueEnd<AclBuilder, AclView>* GetAclQueueEnd() override;
+
+ void Disconnect(uint16_t handle, ErrorCode reason) override;
+
protected:
void ListDependencies(ModuleList* list) const override;
void Start() override;
@@ -66,6 +76,7 @@
private:
void InitEmptyCommand();
+ void do_disconnect(uint16_t handle, ErrorCode reason);
// Handler-only state. Mutexes are not needed when accessing these fields.
std::list<common::ContextualOnceCallback<void(CommandCompleteView)>> command_complete_callbacks;
@@ -73,9 +84,12 @@
std::map<EventCode, common::ContextualCallback<void(EventView)>> registered_events_;
std::map<SubeventCode, common::ContextualCallback<void(LeMetaEventView)>> registered_le_events_;
- // Most operations must acquire this mutex before manipulating shared state. The ONLY exception is blocking on a
- // promise, IF your thread is the only one mutating it. Note that SETTING a promise REQUIRES a lock, since another
- // thread may replace the promise while you are doing so.
+ // thread-safe
+ common::BidiQueue<AclView, AclBuilder> acl_queue_{3 /* TODO: Set queue depth */};
+
+ // Most operations must acquire this mutex before manipulating shared state. The ONLY exception
+ // is blocking on a promise, IF your thread is the only one mutating it. Note that SETTING a
+ // promise REQUIRES a lock, since another thread may replace the promise while you are doing so.
mutable std::mutex mutex_{};
// Shared state between the test and stack threads
@@ -83,13 +97,14 @@
// We start with Consumed=Set, Command=Unset.
// When a command is enqueued, we set Command=set
- // When a command is popped, we block until Command=Set, then (if the queue is now empty) we reset Command=Unset and
- // set Consumed=Set. This way we emulate a blocking queue.
- std::promise<void> command_promise_{}; // Set when at least one command is in the queue
- std::future<void> command_future_ = command_promise_.get_future(); // GetCommand() blocks until this is fulfilled
+ // When a command is popped, we block until Command=Set, then (if the queue is now empty) we
+ // reset Command=Unset and set Consumed=Set. This way we emulate a blocking queue.
+ std::promise<void> command_promise_{}; // Set when at least one command is in the queue
+ std::future<void> command_future_ =
+ command_promise_.get_future(); // GetCommand() blocks until this is fulfilled
- CommandView empty_command_view_ =
- CommandView::Create(PacketView<packet::kLittleEndian>(std::make_shared<std::vector<uint8_t>>()));
+ CommandView empty_command_view_ = CommandView::Create(
+ PacketView<packet::kLittleEndian>(std::make_shared<std::vector<uint8_t>>()));
};
} // namespace hci
diff --git a/system/gd/hci/hci_layer_test.cc b/system/gd/hci/hci_layer_test.cc
index b0aaeaa..8735dcb 100644
--- a/system/gd/hci/hci_layer_test.cc
+++ b/system/gd/hci/hci_layer_test.cc
@@ -60,6 +60,7 @@
namespace bluetooth {
namespace hci {
+namespace {
constexpr std::chrono::milliseconds kTimeout = HciLayer::kHciTimeoutMs / 2;
constexpr std::chrono::milliseconds kAclTimeout = std::chrono::milliseconds(1000);
@@ -83,18 +84,18 @@
void sendHciCommand(hal::HciPacket command) override {
outgoing_commands_.push_back(std::move(command));
if (sent_command_promise_ != nullptr) {
- auto promise = std::move(sent_command_promise_);
- sent_command_promise_.reset();
- promise->set_value();
+ std::promise<void>* prom = sent_command_promise_.release();
+ prom->set_value();
+ delete prom;
}
}
void sendAclData(hal::HciPacket data) override {
outgoing_acl_.push_back(std::move(data));
if (sent_acl_promise_ != nullptr) {
- auto promise = std::move(sent_acl_promise_);
- sent_acl_promise_.reset();
- promise->set_value();
+ std::promise<void>* prom = sent_acl_promise_.release();
+ prom->set_value();
+ delete prom;
}
}
@@ -105,9 +106,9 @@
void sendIsoData(hal::HciPacket data) override {
outgoing_iso_.push_back(std::move(data));
if (sent_iso_promise_ != nullptr) {
- auto promise = std::move(sent_iso_promise_);
- sent_iso_promise_.reset();
- promise->set_value();
+ std::promise<void>* prom = sent_iso_promise_.release();
+ prom->set_value();
+ delete prom;
}
}
@@ -290,7 +291,7 @@
hci_->GetIsoQueueEnd()->UnregisterDequeue();
}
- void ListDependencies(ModuleList* list) {
+ void ListDependencies(ModuleList* list) const {
list->add<HciLayer>();
}
@@ -390,14 +391,14 @@
ASSERT_EQ(reset_sent_status, std::future_status::ready);
// Verify that reset was received
- ASSERT_EQ(1, hal->GetNumSentCommands());
+ ASSERT_EQ(1u, hal->GetNumSentCommands());
auto sent_command = hal->GetSentCommand();
auto reset_view = ResetView::Create(CommandView::Create(sent_command));
ASSERT_TRUE(reset_view.IsValid());
// Verify that only one was sent
- ASSERT_EQ(0, hal->GetNumSentCommands());
+ ASSERT_EQ(0u, hal->GetNumSentCommands());
// Send the response event
uint8_t num_packets = 1;
@@ -457,7 +458,7 @@
ASSERT_TRUE(LeConnectionCompleteView::Create(LeMetaEventView::Create(EventView::Create(event))).IsValid());
}
-TEST_F(HciTest, hciTimeOut) {
+TEST_F(HciTest, DISABLED_hciTimeOut) {
auto event_future = upper->GetReceivedEventFuture();
auto reset_command_future = hal->GetSentCommandFuture();
upper->SendHciCommandExpectingComplete(ResetBuilder::Create());
@@ -478,7 +479,7 @@
}
TEST_F(HciTest, noOpCredits) {
- ASSERT_EQ(0, hal->GetNumSentCommands());
+ ASSERT_EQ(0u, hal->GetNumSentCommands());
// Send 0 credits
uint8_t num_packets = 0;
@@ -488,7 +489,7 @@
upper->SendHciCommandExpectingComplete(ReadLocalVersionInformationBuilder::Create());
// Verify that nothing was sent
- ASSERT_EQ(0, hal->GetNumSentCommands());
+ ASSERT_EQ(0u, hal->GetNumSentCommands());
num_packets = 1;
hal->callbacks->hciEventReceived(GetPacketBytes(NoCommandCompleteBuilder::Create(num_packets)));
@@ -497,7 +498,7 @@
ASSERT_EQ(command_sent_status, std::future_status::ready);
// Verify that one was sent
- ASSERT_EQ(1, hal->GetNumSentCommands());
+ ASSERT_EQ(1u, hal->GetNumSentCommands());
auto event_future = upper->GetReceivedEventFuture();
@@ -522,7 +523,7 @@
}
TEST_F(HciTest, creditsTest) {
- ASSERT_EQ(0, hal->GetNumSentCommands());
+ ASSERT_EQ(0u, hal->GetNumSentCommands());
auto command_future = hal->GetSentCommandFuture();
@@ -535,14 +536,14 @@
ASSERT_EQ(command_sent_status, std::future_status::ready);
// Verify that the first one is sent
- ASSERT_EQ(1, hal->GetNumSentCommands());
+ ASSERT_EQ(1u, hal->GetNumSentCommands());
auto sent_command = hal->GetSentCommand();
auto version_view = ReadLocalVersionInformationView::Create(CommandView::Create(sent_command));
ASSERT_TRUE(version_view.IsValid());
// Verify that only one was sent
- ASSERT_EQ(0, hal->GetNumSentCommands());
+ ASSERT_EQ(0u, hal->GetNumSentCommands());
// Get a new future
auto event_future = upper->GetReceivedEventFuture();
@@ -570,14 +571,14 @@
// Verify that the second one is sent
command_sent_status = command_future.wait_for(kTimeout);
ASSERT_EQ(command_sent_status, std::future_status::ready);
- ASSERT_EQ(1, hal->GetNumSentCommands());
+ ASSERT_EQ(1u, hal->GetNumSentCommands());
sent_command = hal->GetSentCommand();
auto supported_commands_view = ReadLocalSupportedCommandsView::Create(CommandView::Create(sent_command));
ASSERT_TRUE(supported_commands_view.IsValid());
// Verify that only one was sent
- ASSERT_EQ(0, hal->GetNumSentCommands());
+ ASSERT_EQ(0u, hal->GetNumSentCommands());
event_future = upper->GetReceivedEventFuture();
command_future = hal->GetSentCommandFuture();
@@ -598,14 +599,14 @@
// Verify that the third one is sent
command_sent_status = command_future.wait_for(kTimeout);
ASSERT_EQ(command_sent_status, std::future_status::ready);
- ASSERT_EQ(1, hal->GetNumSentCommands());
+ ASSERT_EQ(1u, hal->GetNumSentCommands());
sent_command = hal->GetSentCommand();
auto supported_features_view = ReadLocalSupportedFeaturesView::Create(CommandView::Create(sent_command));
ASSERT_TRUE(supported_features_view.IsValid());
// Verify that only one was sent
- ASSERT_EQ(0, hal->GetNumSentCommands());
+ ASSERT_EQ(0u, hal->GetNumSentCommands());
event_future = upper->GetReceivedEventFuture();
// Send the response event
@@ -631,7 +632,7 @@
// Check the command
auto sent_command = hal->GetSentCommand();
- ASSERT_LT(0, sent_command.size());
+ ASSERT_LT(0u, sent_command.size());
LeRandView view = LeRandView::Create(LeSecurityCommandView::Create(CommandView::Create(sent_command)));
ASSERT_TRUE(view.IsValid());
@@ -662,7 +663,7 @@
// Check the command
auto sent_command = hal->GetSentCommand();
- ASSERT_LT(0, sent_command.size());
+ ASSERT_LT(0u, sent_command.size());
auto view = WriteSimplePairingModeView::Create(SecurityCommandView::Create(CommandView::Create(sent_command)));
ASSERT_TRUE(view.IsValid());
@@ -699,7 +700,7 @@
// Check the command
auto sent_command = hal->GetSentCommand();
- ASSERT_LT(0, sent_command.size());
+ ASSERT_LT(0u, sent_command.size());
CreateConnectionView view = CreateConnectionView::Create(
ConnectionManagementCommandView::Create(AclCommandView::Create(CommandView::Create(sent_command))));
ASSERT_TRUE(view.IsValid());
@@ -776,7 +777,7 @@
auto sent_acl_status = sent_acl_future.wait_for(kAclTimeout);
ASSERT_EQ(sent_acl_status, std::future_status::ready);
auto sent_acl = hal->GetSentAcl();
- ASSERT_LT(0, sent_acl.size());
+ ASSERT_LT(0u, sent_acl.size());
AclView sent_acl_view = AclView::Create(sent_acl);
ASSERT_TRUE(sent_acl_view.IsValid());
ASSERT_EQ(bd_addr.length() + sizeof(handle), sent_acl_view.GetPayload().size());
@@ -904,5 +905,7 @@
ASSERT_EQ(handle, itr.extract<uint16_t>());
ASSERT_EQ(received_packets, itr.extract<uint16_t>());
}
+
+} // namespace
} // namespace hci
} // namespace bluetooth
diff --git a/system/gd/hci/hci_layer_unittest.cc b/system/gd/hci/hci_layer_unittest.cc
index 3d88f06..d4c2423 100644
--- a/system/gd/hci/hci_layer_unittest.cc
+++ b/system/gd/hci/hci_layer_unittest.cc
@@ -168,11 +168,13 @@
TEST_F(HciLayerTest, setup_teardown) {}
-TEST_F(HciLayerTest, reset_command_sent_on_start) {
+// b/260915548
+TEST_F(HciLayerTest, DISABLED_reset_command_sent_on_start) {
FailIfResetNotSent();
}
-TEST_F(HciLayerTest, controller_debug_info_requested_on_hci_timeout) {
+// b/260915548
+TEST_F(HciLayerTest, DISABLED_controller_debug_info_requested_on_hci_timeout) {
FailIfResetNotSent();
FakeTimerAdvance(HciLayer::kHciTimeoutMs.count());
@@ -183,7 +185,8 @@
ASSERT_TRUE(debug_info_view.IsValid());
}
-TEST_F(HciLayerTest, abort_after_hci_restart_timeout) {
+// b/260915548
+TEST_F(HciLayerTest, DISABLED_abort_after_hci_restart_timeout) {
FailIfResetNotSent();
FakeTimerAdvance(HciLayer::kHciTimeoutMs.count());
@@ -202,7 +205,8 @@
"");
}
-TEST_F(HciLayerTest, abort_on_root_inflammation_event) {
+// b/260915548
+TEST_F(HciLayerTest, DISABLED_abort_on_root_inflammation_event) {
FailIfResetNotSent();
auto payload = CreatePayload({'0'});
diff --git a/system/gd/hci/le_address_manager_test.cc b/system/gd/hci/le_address_manager_test.cc
index 5008db5..7f698f0 100644
--- a/system/gd/hci/le_address_manager_test.cc
+++ b/system/gd/hci/le_address_manager_test.cc
@@ -26,22 +26,28 @@
using ::bluetooth::os::Handler;
using ::bluetooth::os::Thread;
-namespace bluetooth {
-namespace hci {
-
namespace {
-using packet::kLittleEndian;
-using packet::PacketView;
-using packet::RawBuilder;
-PacketView<kLittleEndian> GetPacketView(std::unique_ptr<packet::BasePacketBuilder> packet) {
+using namespace bluetooth;
+
+packet::PacketView<packet::kLittleEndian> GetPacketView(std::unique_ptr<packet::BasePacketBuilder> packet) {
auto bytes = std::make_shared<std::vector<uint8_t>>();
- BitInserter i(*bytes);
+ packet::BitInserter i(*bytes);
bytes->reserve(packet->size());
packet->Serialize(i);
return packet::PacketView<packet::kLittleEndian>(bytes);
}
+} // namespace
+
+namespace bluetooth {
+namespace hci {
+namespace {
+
+using packet::kLittleEndian;
+using packet::PacketView;
+using packet::RawBuilder;
+
class TestHciLayer : public HciLayer {
public:
void EnqueueCommand(
@@ -51,13 +57,14 @@
command_queue_.push(std::move(command));
command_complete_callbacks.push_back(std::move(on_complete));
if (command_promise_ != nullptr) {
- command_promise_->set_value();
- command_promise_.reset();
+ std::promise<void>* prom = command_promise_.release();
+ prom->set_value();
+ delete prom;
}
}
void SetCommandFuture() {
- ASSERT_LOG(command_promise_ == nullptr, "Promises, Promises, ... Only one at a time.");
+ ASSERT_EQ(command_promise_, nullptr) << "Promises, Promises, ... Only one at a time.";
command_promise_ = std::make_unique<std::promise<void>>();
command_future_ = std::make_unique<std::future<void>>(command_promise_->get_future());
}
@@ -130,8 +137,9 @@
paused = false;
le_address_manager_->AckResume(this);
if (resume_promise_ != nullptr) {
- resume_promise_->set_value();
- resume_promise_.reset();
+ std::promise<void>* prom = resume_promise_.release();
+ prom->set_value();
+ delete prom;
}
}
@@ -217,10 +225,11 @@
LeAddressManager::AddressPolicy::USE_RESOLVABLE_ADDRESS,
remote_address,
irk,
+ false,
minimum_rotation_time,
maximum_rotation_time);
- test_hci_layer_->SetCommandFuture();
+ ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
le_address_manager_->Register(clients[0].get());
sync_handler(handler_);
test_hci_layer_->GetCommand(OpCode::LE_SET_RANDOM_ADDRESS);
@@ -239,10 +248,11 @@
LeAddressManager::AddressPolicy::USE_NON_RESOLVABLE_ADDRESS,
remote_address,
irk,
+ false,
minimum_rotation_time,
maximum_rotation_time);
- test_hci_layer_->SetCommandFuture();
+ ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
le_address_manager_->Register(clients[0].get());
sync_handler(handler_);
test_hci_layer_->GetCommand(OpCode::LE_SET_RANDOM_ADDRESS);
@@ -263,6 +273,7 @@
LeAddressManager::AddressPolicy::USE_RESOLVABLE_ADDRESS,
remote_address,
irk,
+ false,
minimum_rotation_time,
maximum_rotation_time);
le_address_manager_->Register(clients[0].get());
@@ -300,10 +311,11 @@
LeAddressManager::AddressPolicy::USE_RESOLVABLE_ADDRESS,
remote_address,
irk,
+ false,
minimum_rotation_time,
maximum_rotation_time);
- test_hci_layer_->SetCommandFuture();
+ ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
le_address_manager_->Register(clients[0].get());
sync_handler(handler_);
test_hci_layer_->GetCommand(OpCode::LE_SET_RANDOM_ADDRESS);
@@ -330,7 +342,7 @@
TEST_F(LeAddressManagerWithSingleClientTest, add_device_to_connect_list) {
Address address;
Address::FromString("01:02:03:04:05:06", address);
- test_hci_layer_->SetCommandFuture();
+ ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
le_address_manager_->AddDeviceToFilterAcceptList(FilterAcceptListAddressType::RANDOM, address);
auto packet = test_hci_layer_->GetCommand(OpCode::LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST);
auto packet_view = LeAddDeviceToFilterAcceptListView::Create(
@@ -346,12 +358,12 @@
TEST_F(LeAddressManagerWithSingleClientTest, remove_device_from_connect_list) {
Address address;
Address::FromString("01:02:03:04:05:06", address);
- test_hci_layer_->SetCommandFuture();
+ ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
le_address_manager_->AddDeviceToFilterAcceptList(FilterAcceptListAddressType::RANDOM, address);
test_hci_layer_->GetCommand(OpCode::LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST);
test_hci_layer_->IncomingEvent(LeAddDeviceToFilterAcceptListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
- test_hci_layer_->SetCommandFuture();
+ ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
le_address_manager_->RemoveDeviceFromFilterAcceptList(FilterAcceptListAddressType::RANDOM, address);
auto packet = test_hci_layer_->GetCommand(OpCode::LE_REMOVE_DEVICE_FROM_FILTER_ACCEPT_LIST);
auto packet_view = LeRemoveDeviceFromFilterAcceptListView::Create(
@@ -366,84 +378,154 @@
TEST_F(LeAddressManagerWithSingleClientTest, clear_connect_list) {
Address address;
Address::FromString("01:02:03:04:05:06", address);
- test_hci_layer_->SetCommandFuture();
+ ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
le_address_manager_->AddDeviceToFilterAcceptList(FilterAcceptListAddressType::RANDOM, address);
test_hci_layer_->GetCommand(OpCode::LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST);
test_hci_layer_->IncomingEvent(LeAddDeviceToFilterAcceptListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
- test_hci_layer_->SetCommandFuture();
+ ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
le_address_manager_->ClearFilterAcceptList();
test_hci_layer_->GetCommand(OpCode::LE_CLEAR_FILTER_ACCEPT_LIST);
test_hci_layer_->IncomingEvent(LeClearFilterAcceptListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
clients[0].get()->WaitForResume();
}
-TEST_F(LeAddressManagerWithSingleClientTest, add_device_to_resolving_list) {
+// b/260916288
+TEST_F(LeAddressManagerWithSingleClientTest, DISABLED_add_device_to_resolving_list) {
Address address;
Address::FromString("01:02:03:04:05:06", address);
Octet16 peer_irk = {0xec, 0x02, 0x34, 0xa3, 0x57, 0xc8, 0xad, 0x05, 0x34, 0x10, 0x10, 0xa6, 0x0a, 0x39, 0x7d, 0x9b};
Octet16 local_irk = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10};
- test_hci_layer_->SetCommandFuture();
+
+ ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
le_address_manager_->AddDeviceToResolvingList(
PeerAddressType::RANDOM_DEVICE_OR_IDENTITY_ADDRESS, address, peer_irk, local_irk);
- auto packet = test_hci_layer_->GetCommand(OpCode::LE_ADD_DEVICE_TO_RESOLVING_LIST);
- auto packet_view = LeAddDeviceToResolvingListView::Create(LeSecurityCommandView::Create(packet));
- ASSERT_TRUE(packet_view.IsValid());
- ASSERT_EQ(PeerAddressType::RANDOM_DEVICE_OR_IDENTITY_ADDRESS, packet_view.GetPeerIdentityAddressType());
- ASSERT_EQ(address, packet_view.GetPeerIdentityAddress());
- ASSERT_EQ(peer_irk, packet_view.GetPeerIrk());
- ASSERT_EQ(local_irk, packet_view.GetLocalIrk());
-
- test_hci_layer_->IncomingEvent(LeAddDeviceToResolvingListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
+ {
+ auto packet = test_hci_layer_->GetCommand(OpCode::LE_SET_ADDRESS_RESOLUTION_ENABLE);
+ auto packet_view = LeSetAddressResolutionEnableView::Create(LeSecurityCommandView::Create(packet));
+ ASSERT_TRUE(packet_view.IsValid());
+ ASSERT_EQ(Enable::DISABLED, packet_view.GetAddressResolutionEnable());
+ test_hci_layer_->IncomingEvent(LeSetAddressResolutionEnableCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
+ }
+ {
+ ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
+ auto packet = test_hci_layer_->GetCommand(OpCode::LE_ADD_DEVICE_TO_RESOLVING_LIST);
+ auto packet_view = LeAddDeviceToResolvingListView::Create(LeSecurityCommandView::Create(packet));
+ ASSERT_TRUE(packet_view.IsValid());
+ ASSERT_EQ(PeerAddressType::RANDOM_DEVICE_OR_IDENTITY_ADDRESS, packet_view.GetPeerIdentityAddressType());
+ ASSERT_EQ(address, packet_view.GetPeerIdentityAddress());
+ ASSERT_EQ(peer_irk, packet_view.GetPeerIrk());
+ ASSERT_EQ(local_irk, packet_view.GetLocalIrk());
+ test_hci_layer_->IncomingEvent(LeAddDeviceToResolvingListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
+ }
+ {
+ ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
+ auto packet = test_hci_layer_->GetCommand(OpCode::LE_SET_ADDRESS_RESOLUTION_ENABLE);
+ auto packet_view = LeSetAddressResolutionEnableView::Create(LeSecurityCommandView::Create(packet));
+ ASSERT_TRUE(packet_view.IsValid());
+ ASSERT_EQ(Enable::ENABLED, packet_view.GetAddressResolutionEnable());
+ test_hci_layer_->IncomingEvent(LeSetAddressResolutionEnableCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
+ }
clients[0].get()->WaitForResume();
}
-TEST_F(LeAddressManagerWithSingleClientTest, remove_device_from_resolving_list) {
+// b/260916288
+TEST_F(LeAddressManagerWithSingleClientTest, DISABLED_remove_device_from_resolving_list) {
Address address;
Address::FromString("01:02:03:04:05:06", address);
Octet16 peer_irk = {0xec, 0x02, 0x34, 0xa3, 0x57, 0xc8, 0xad, 0x05, 0x34, 0x10, 0x10, 0xa6, 0x0a, 0x39, 0x7d, 0x9b};
Octet16 local_irk = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10};
- test_hci_layer_->SetCommandFuture();
+ ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
le_address_manager_->AddDeviceToResolvingList(
PeerAddressType::RANDOM_DEVICE_OR_IDENTITY_ADDRESS, address, peer_irk, local_irk);
+ test_hci_layer_->GetCommand(OpCode::LE_SET_ADDRESS_RESOLUTION_ENABLE);
+ test_hci_layer_->IncomingEvent(LeSetAddressResolutionEnableCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
+ ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
test_hci_layer_->GetCommand(OpCode::LE_ADD_DEVICE_TO_RESOLVING_LIST);
test_hci_layer_->IncomingEvent(LeAddDeviceToResolvingListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
+ ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
+ test_hci_layer_->GetCommand(OpCode::LE_SET_ADDRESS_RESOLUTION_ENABLE);
+ test_hci_layer_->IncomingEvent(LeSetAddressResolutionEnableCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
- test_hci_layer_->SetCommandFuture();
+ ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
le_address_manager_->RemoveDeviceFromResolvingList(PeerAddressType::RANDOM_DEVICE_OR_IDENTITY_ADDRESS, address);
- auto packet = test_hci_layer_->GetCommand(OpCode::LE_REMOVE_DEVICE_FROM_RESOLVING_LIST);
- auto packet_view = LeRemoveDeviceFromResolvingListView::Create(LeSecurityCommandView::Create(packet));
- ASSERT_TRUE(packet_view.IsValid());
- ASSERT_EQ(PeerAddressType::RANDOM_DEVICE_OR_IDENTITY_ADDRESS, packet_view.GetPeerIdentityAddressType());
- ASSERT_EQ(address, packet_view.GetPeerIdentityAddress());
- test_hci_layer_->IncomingEvent(LeRemoveDeviceFromResolvingListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
+ {
+ auto packet = test_hci_layer_->GetCommand(OpCode::LE_SET_ADDRESS_RESOLUTION_ENABLE);
+ auto packet_view = LeSetAddressResolutionEnableView::Create(LeSecurityCommandView::Create(packet));
+ ASSERT_TRUE(packet_view.IsValid());
+ ASSERT_EQ(Enable::DISABLED, packet_view.GetAddressResolutionEnable());
+ test_hci_layer_->IncomingEvent(LeSetAddressResolutionEnableCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
+ }
+ {
+ ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
+ auto packet = test_hci_layer_->GetCommand(OpCode::LE_REMOVE_DEVICE_FROM_RESOLVING_LIST);
+ auto packet_view = LeRemoveDeviceFromResolvingListView::Create(LeSecurityCommandView::Create(packet));
+ ASSERT_TRUE(packet_view.IsValid());
+ ASSERT_EQ(PeerAddressType::RANDOM_DEVICE_OR_IDENTITY_ADDRESS, packet_view.GetPeerIdentityAddressType());
+ ASSERT_EQ(address, packet_view.GetPeerIdentityAddress());
+ test_hci_layer_->IncomingEvent(LeRemoveDeviceFromResolvingListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
+ }
+ {
+ ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
+ auto packet = test_hci_layer_->GetCommand(OpCode::LE_SET_ADDRESS_RESOLUTION_ENABLE);
+ auto packet_view = LeSetAddressResolutionEnableView::Create(LeSecurityCommandView::Create(packet));
+ ASSERT_TRUE(packet_view.IsValid());
+ ASSERT_EQ(Enable::ENABLED, packet_view.GetAddressResolutionEnable());
+ test_hci_layer_->IncomingEvent(LeSetAddressResolutionEnableCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
+ }
clients[0].get()->WaitForResume();
}
-TEST_F(LeAddressManagerWithSingleClientTest, clear_resolving_list) {
+// b/260916288
+TEST_F(LeAddressManagerWithSingleClientTest, DISABLED_clear_resolving_list) {
Address address;
Address::FromString("01:02:03:04:05:06", address);
Octet16 peer_irk = {0xec, 0x02, 0x34, 0xa3, 0x57, 0xc8, 0xad, 0x05, 0x34, 0x10, 0x10, 0xa6, 0x0a, 0x39, 0x7d, 0x9b};
Octet16 local_irk = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10};
- test_hci_layer_->SetCommandFuture();
+ ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
le_address_manager_->AddDeviceToResolvingList(
PeerAddressType::RANDOM_DEVICE_OR_IDENTITY_ADDRESS, address, peer_irk, local_irk);
+ test_hci_layer_->GetCommand(OpCode::LE_SET_ADDRESS_RESOLUTION_ENABLE);
+ test_hci_layer_->IncomingEvent(LeSetAddressResolutionEnableCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
+ ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
test_hci_layer_->GetCommand(OpCode::LE_ADD_DEVICE_TO_RESOLVING_LIST);
test_hci_layer_->IncomingEvent(LeAddDeviceToResolvingListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
+ ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
+ test_hci_layer_->GetCommand(OpCode::LE_SET_ADDRESS_RESOLUTION_ENABLE);
+ test_hci_layer_->IncomingEvent(LeSetAddressResolutionEnableCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
- test_hci_layer_->SetCommandFuture();
+ ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
le_address_manager_->ClearResolvingList();
- auto packet = test_hci_layer_->GetCommand(OpCode::LE_CLEAR_RESOLVING_LIST);
- auto packet_view = LeClearResolvingListView::Create(LeSecurityCommandView::Create(packet));
- ASSERT_TRUE(packet_view.IsValid());
- test_hci_layer_->IncomingEvent(LeClearResolvingListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
+ {
+ auto packet = test_hci_layer_->GetCommand(OpCode::LE_SET_ADDRESS_RESOLUTION_ENABLE);
+ auto packet_view = LeSetAddressResolutionEnableView::Create(LeSecurityCommandView::Create(packet));
+ ASSERT_TRUE(packet_view.IsValid());
+ ASSERT_EQ(Enable::DISABLED, packet_view.GetAddressResolutionEnable());
+ test_hci_layer_->IncomingEvent(LeSetAddressResolutionEnableCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
+ }
+ {
+ ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
+ auto packet = test_hci_layer_->GetCommand(OpCode::LE_CLEAR_RESOLVING_LIST);
+ auto packet_view = LeClearResolvingListView::Create(LeSecurityCommandView::Create(packet));
+ ASSERT_TRUE(packet_view.IsValid());
+ test_hci_layer_->IncomingEvent(LeClearResolvingListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
+ }
+ {
+ ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
+ auto packet = test_hci_layer_->GetCommand(OpCode::LE_SET_ADDRESS_RESOLUTION_ENABLE);
+ auto packet_view = LeSetAddressResolutionEnableView::Create(LeSecurityCommandView::Create(packet));
+ ASSERT_TRUE(packet_view.IsValid());
+ ASSERT_EQ(Enable::ENABLED, packet_view.GetAddressResolutionEnable());
+ test_hci_layer_->IncomingEvent(LeSetAddressResolutionEnableCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
+ }
+
clients[0].get()->WaitForResume();
}
TEST_F(LeAddressManagerWithSingleClientTest, register_during_command_complete) {
Address address;
Address::FromString("01:02:03:04:05:06", address);
- test_hci_layer_->SetCommandFuture();
+ ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
le_address_manager_->AddDeviceToFilterAcceptList(FilterAcceptListAddressType::RANDOM, address);
auto packet = test_hci_layer_->GetCommand(OpCode::LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST);
auto packet_view = LeAddDeviceToFilterAcceptListView::Create(
@@ -454,7 +536,7 @@
test_hci_layer_->IncomingEvent(LeAddDeviceToFilterAcceptListCompleteBuilder::Create(0x01, ErrorCode::SUCCESS));
AllocateClients(1);
- test_hci_layer_->SetCommandFuture();
+ ASSERT_NO_FATAL_FAILURE(test_hci_layer_->SetCommandFuture());
le_address_manager_->Register(clients[1].get());
clients[0].get()->WaitForResume();
clients[1].get()->WaitForResume();
diff --git a/system/gd/hci/le_periodic_sync_manager_test.cc b/system/gd/hci/le_periodic_sync_manager_test.cc
index 3bb9e42..da77d18 100644
--- a/system/gd/hci/le_periodic_sync_manager_test.cc
+++ b/system/gd/hci/le_periodic_sync_manager_test.cc
@@ -45,8 +45,9 @@
command_queue_.push(std::move(command));
command_complete_callbacks.push_back(std::move(on_complete));
if (command_promise_ != nullptr) {
- command_promise_->set_value();
- command_promise_.reset();
+ std::promise<void>* prom = command_promise_.release();
+ prom->set_value();
+ delete prom;
}
}
@@ -56,13 +57,14 @@
command_queue_.push(std::move(command));
command_status_callbacks.push_back(std::move(on_status));
if (command_promise_ != nullptr) {
- command_promise_->set_value();
- command_promise_.reset();
+ std::promise<void>* prom = command_promise_.release();
+ prom->set_value();
+ delete prom;
}
}
void SetCommandFuture() {
- ASSERT_LOG(command_promise_ == nullptr, "Promises, Promises, ... Only one at a time.");
+ ASSERT_EQ(command_promise_, nullptr) << "Promises, Promises, ... Only one at a time.";
command_promise_ = std::make_unique<std::promise<void>>();
command_future_ = std::make_unique<std::future<void>>(command_promise_->get_future());
}
@@ -224,7 +226,7 @@
};
uint16_t skip = 0x04;
uint16_t sync_timeout = 0x0A;
- test_le_scanning_interface_->SetCommandFuture();
+ ASSERT_NO_FATAL_FAILURE(test_le_scanning_interface_->SetCommandFuture());
periodic_sync_manager_->StartSync(request, skip, sync_timeout);
auto packet = test_le_scanning_interface_->GetCommand(OpCode::LE_PERIODIC_ADVERTISING_CREATE_SYNC);
auto packet_view = LePeriodicAdvertisingCreateSyncView::Create(LeScanningCommandView::Create(packet));
@@ -251,7 +253,7 @@
.sync_handle = sync_handle,
.sync_state = PeriodicSyncState::PERIODIC_SYNC_STATE_IDLE,
};
- test_le_scanning_interface_->SetCommandFuture();
+ ASSERT_NO_FATAL_FAILURE(test_le_scanning_interface_->SetCommandFuture());
periodic_sync_manager_->StartSync(request, 0x04, 0x0A);
auto packet = test_le_scanning_interface_->GetCommand(OpCode::LE_PERIODIC_ADVERTISING_CREATE_SYNC);
auto temp_view = LePeriodicAdvertisingCreateSyncView::Create(LeScanningCommandView::Create(packet));
@@ -293,7 +295,7 @@
.sync_handle = sync_handle,
.sync_state = PeriodicSyncState::PERIODIC_SYNC_STATE_IDLE,
};
- test_le_scanning_interface_->SetCommandFuture();
+ ASSERT_NO_FATAL_FAILURE(test_le_scanning_interface_->SetCommandFuture());
periodic_sync_manager_->StartSync(request, 0x04, 0x0A);
auto packet = test_le_scanning_interface_->GetCommand(OpCode::LE_PERIODIC_ADVERTISING_CREATE_SYNC);
auto temp_view = LePeriodicAdvertisingCreateSyncView::Create(LeScanningCommandView::Create(packet));
@@ -335,7 +337,7 @@
.sync_handle = sync_handle,
.sync_state = PeriodicSyncState::PERIODIC_SYNC_STATE_IDLE,
};
- test_le_scanning_interface_->SetCommandFuture();
+ ASSERT_NO_FATAL_FAILURE(test_le_scanning_interface_->SetCommandFuture());
periodic_sync_manager_->StartSync(request, 0x04, 0x0A);
auto packet = test_le_scanning_interface_->GetCommand(OpCode::LE_PERIODIC_ADVERTISING_CREATE_SYNC);
auto temp_veiw = LePeriodicAdvertisingCreateSyncView::Create(LeScanningCommandView::Create(packet));
@@ -362,7 +364,7 @@
periodic_sync_manager_->HandleLePeriodicAdvertisingSyncEstablished(event_view);
// StopSync
- test_le_scanning_interface_->SetCommandFuture();
+ ASSERT_NO_FATAL_FAILURE(test_le_scanning_interface_->SetCommandFuture());
periodic_sync_manager_->StopSync(sync_handle);
packet = test_le_scanning_interface_->GetCommand(OpCode::LE_PERIODIC_ADVERTISING_TERMINATE_SYNC);
auto packet_view = LePeriodicAdvertisingTerminateSyncView::Create(LeScanningCommandView::Create(packet));
@@ -385,7 +387,7 @@
.sync_handle = sync_handle,
.sync_state = PeriodicSyncState::PERIODIC_SYNC_STATE_IDLE,
};
- test_le_scanning_interface_->SetCommandFuture();
+ ASSERT_NO_FATAL_FAILURE(test_le_scanning_interface_->SetCommandFuture());
periodic_sync_manager_->StartSync(request, 0x04, 0x0A);
auto packet = test_le_scanning_interface_->GetCommand(OpCode::LE_PERIODIC_ADVERTISING_CREATE_SYNC);
auto temp_veiw = LePeriodicAdvertisingCreateSyncView::Create(LeScanningCommandView::Create(packet));
@@ -396,7 +398,7 @@
LePeriodicAdvertisingCreateSyncStatusBuilder::Create(ErrorCode::SUCCESS, 0x00));
// Cancel crate sync
- test_le_scanning_interface_->SetCommandFuture();
+ ASSERT_NO_FATAL_FAILURE(test_le_scanning_interface_->SetCommandFuture());
periodic_sync_manager_->CancelCreateSync(advertiser_sid, address_with_type.GetAddress());
packet = test_le_scanning_interface_->GetCommand(OpCode::LE_PERIODIC_ADVERTISING_CREATE_SYNC_CANCEL);
auto packet_view = LePeriodicAdvertisingCreateSyncCancelView::Create(LeScanningCommandView::Create(packet));
@@ -411,7 +413,7 @@
uint16_t sync_handle = 0x11;
uint16_t connection_handle = 0x12;
int pa_source = 0x01;
- test_le_scanning_interface_->SetCommandFuture();
+ ASSERT_NO_FATAL_FAILURE(test_le_scanning_interface_->SetCommandFuture());
periodic_sync_manager_->TransferSync(address, service_data, sync_handle, pa_source, connection_handle);
auto packet = test_le_scanning_interface_->GetCommand(OpCode::LE_PERIODIC_ADVERTISING_SYNC_TRANSFER);
auto packet_view = LePeriodicAdvertisingSyncTransferView::Create(LeScanningCommandView::Create(packet));
@@ -436,7 +438,7 @@
uint16_t advertising_handle = 0x11;
uint16_t connection_handle = 0x12;
int pa_source = 0x01;
- test_le_scanning_interface_->SetCommandFuture();
+ ASSERT_NO_FATAL_FAILURE(test_le_scanning_interface_->SetCommandFuture());
periodic_sync_manager_->SyncSetInfo(address, service_data, advertising_handle, pa_source, connection_handle);
auto packet = test_le_scanning_interface_->GetCommand(OpCode::LE_PERIODIC_ADVERTISING_SET_INFO_TRANSFER);
auto packet_view = LePeriodicAdvertisingSetInfoTransferView::Create(LeScanningCommandView::Create(packet));
@@ -461,7 +463,7 @@
uint16_t skip = 0x11;
uint16_t timout = 0x12;
int reg_id = 0x01;
- test_le_scanning_interface_->SetCommandFuture();
+ ASSERT_NO_FATAL_FAILURE(test_le_scanning_interface_->SetCommandFuture());
periodic_sync_manager_->SyncTxParameters(address, mode, skip, timout, reg_id);
auto packet =
test_le_scanning_interface_->GetCommand(OpCode::LE_SET_DEFAULT_PERIODIC_ADVERTISING_SYNC_TRANSFER_PARAMETERS);
@@ -490,7 +492,7 @@
.sync_handle = sync_handle,
.sync_state = PeriodicSyncState::PERIODIC_SYNC_STATE_IDLE,
};
- test_le_scanning_interface_->SetCommandFuture();
+ ASSERT_NO_FATAL_FAILURE(test_le_scanning_interface_->SetCommandFuture());
periodic_sync_manager_->StartSync(request, 0x04, 0x0A);
auto packet = test_le_scanning_interface_->GetCommand(OpCode::LE_PERIODIC_ADVERTISING_CREATE_SYNC);
auto temp_veiw = LePeriodicAdvertisingCreateSyncView::Create(LeScanningCommandView::Create(packet));
@@ -542,7 +544,7 @@
.sync_handle = sync_handle,
.sync_state = PeriodicSyncState::PERIODIC_SYNC_STATE_IDLE,
};
- test_le_scanning_interface_->SetCommandFuture();
+ ASSERT_NO_FATAL_FAILURE(test_le_scanning_interface_->SetCommandFuture());
periodic_sync_manager_->StartSync(request, 0x04, 0x0A);
auto packet = test_le_scanning_interface_->GetCommand(OpCode::LE_PERIODIC_ADVERTISING_CREATE_SYNC);
auto temp_veiw = LePeriodicAdvertisingCreateSyncView::Create(LeScanningCommandView::Create(packet));
diff --git a/system/gd/hci/le_scanning_manager_test.cc b/system/gd/hci/le_scanning_manager_test.cc
index 88eb3be..e59c725 100644
--- a/system/gd/hci/le_scanning_manager_test.cc
+++ b/system/gd/hci/le_scanning_manager_test.cc
@@ -29,6 +29,7 @@
#include <queue>
#include <vector>
+#include "hci/hci_layer_fake.h"
#include "common/bind.h"
#include "hci/acl_manager.h"
#include "hci/address.h"
@@ -109,14 +110,6 @@
namespace hci {
namespace {
-PacketView<kLittleEndian> GetPacketView(std::unique_ptr<packet::BasePacketBuilder> packet) {
- auto bytes = std::make_shared<std::vector<uint8_t>>();
- BitInserter i(*bytes);
- bytes->reserve(packet->size());
- packet->Serialize(i);
- return packet::PacketView<packet::kLittleEndian>(bytes);
-}
-
class TestController : public Controller {
public:
bool IsSupported(OpCode op_code) const override {
@@ -127,6 +120,14 @@
supported_opcodes_.insert(op_code);
}
+ bool SupportsBleExtendedAdvertising() const override {
+ return support_ble_extended_advertising_;
+ }
+
+ void SetBleExtendedAdvertisingSupport(bool support) {
+ support_ble_extended_advertising_ = support;
+ }
+
protected:
void Start() override {}
void Stop() override {}
@@ -134,156 +135,7 @@
private:
std::set<OpCode> supported_opcodes_{};
-};
-
-class TestHciLayer : public HciLayer {
- public:
- void EnqueueCommand(
- std::unique_ptr<CommandBuilder> command,
- common::ContextualOnceCallback<void(CommandStatusView)> on_status) override {
- std::lock_guard<std::mutex> lock(mutex_);
- command_queue_.push(std::move(command));
- command_status_callbacks.push_back(std::move(on_status));
- command_count_--;
- if (command_promise_ != nullptr && command_count_ == 0) {
- command_promise_->set_value();
- command_promise_.reset();
- }
- }
-
- void EnqueueCommand(
- std::unique_ptr<CommandBuilder> command,
- common::ContextualOnceCallback<void(CommandCompleteView)> on_complete) override {
- std::lock_guard<std::mutex> lock(mutex_);
- command_queue_.push(std::move(command));
- command_complete_callbacks.push_back(std::move(on_complete));
- command_count_--;
- if (command_promise_ != nullptr && command_count_ == 0) {
- command_promise_->set_value();
- command_promise_.reset();
- }
- }
-
- // Set command future for 'num_command' commands are expected
- void SetCommandFuture(uint16_t num_command = 1) {
- ASSERT_TRUE(command_promise_ == nullptr) << "Promises, Promises, ... Only one at a time.";
- command_count_ = num_command;
- command_promise_ = std::make_unique<std::promise<void>>();
- command_future_ = command_promise_->get_future();
- }
-
- CommandView GetCommand() {
- // Wait for EnqueueCommand if command_queue_ is empty
- if (command_promise_ != nullptr) {
- if (command_queue_.empty()) {
- LOG_ERROR("Waiting for command queue to fill ");
- command_future_.wait_for(1s);
- }
- command_promise_.reset();
- }
-
- std::lock_guard<std::mutex> lock(mutex_);
- if (command_queue_.empty()) {
- LOG_ERROR("Command queue is empty");
- return empty_command_view_;
- }
-
- auto last = std::move(command_queue_.front());
- command_queue_.pop();
- CommandView command_packet_view = CommandView::Create(GetPacketView(std::move(last)));
- if (!command_packet_view.IsValid()) {
- LOG_ERROR("Got invalid command");
- return empty_command_view_;
- }
- return command_packet_view;
- }
-
- void RegisterEventHandler(EventCode event_code, common::ContextualCallback<void(EventView)> event_handler) override {
- registered_events_[event_code] = event_handler;
- }
-
- void UnregisterEventHandler(EventCode event_code) override {
- registered_events_.erase(event_code);
- }
-
- void RegisterLeEventHandler(
- SubeventCode subevent_code, common::ContextualCallback<void(LeMetaEventView)> event_handler) override {
- registered_le_events_[subevent_code] = event_handler;
- }
-
- void UnregisterLeEventHandler(SubeventCode subevent_code) override {
- registered_le_events_.erase(subevent_code);
- }
-
- void IncomingEvent(std::unique_ptr<EventBuilder> event_builder) {
- auto packet = GetPacketView(std::move(event_builder));
- EventView event = EventView::Create(packet);
- ASSERT_TRUE(event.IsValid());
- EventCode event_code = event.GetEventCode();
- ASSERT_NE(registered_events_.find(event_code), registered_events_.end()) << EventCodeText(event_code);
- registered_events_[event_code].Invoke(event);
- }
-
- void IncomingLeMetaEvent(std::unique_ptr<LeMetaEventBuilder> event_builder) {
- auto packet = GetPacketView(std::move(event_builder));
- EventView event = EventView::Create(packet);
- LeMetaEventView meta_event_view = LeMetaEventView::Create(event);
- ASSERT_TRUE(meta_event_view.IsValid());
- SubeventCode subevent_code = meta_event_view.GetSubeventCode();
- ASSERT_TRUE(registered_le_events_.find(subevent_code) != registered_le_events_.end());
- registered_le_events_[subevent_code].Invoke(meta_event_view);
- }
-
- void CommandCompleteCallback(EventView event) {
- CommandCompleteView complete_view = CommandCompleteView::Create(event);
- ASSERT_TRUE(complete_view.IsValid());
- ASSERT_TRUE(!command_complete_callbacks.empty());
- std::move(command_complete_callbacks.front()).Invoke(complete_view);
- command_complete_callbacks.pop_front();
- }
-
- void CommandStatusCallback(EventView event) {
- CommandStatusView status_view = CommandStatusView::Create(event);
- ASSERT_TRUE(status_view.IsValid());
- std::move(command_status_callbacks.front()).Invoke(status_view);
- command_status_callbacks.pop_front();
- }
-
- void InitEmptyCommand() {
- auto payload = std::make_unique<bluetooth::packet::RawBuilder>();
- auto command_builder = CommandBuilder::Create(OpCode::NONE, std::move(payload));
- empty_command_view_ = CommandView::Create(GetPacketView(std::move(command_builder)));
- ASSERT_TRUE(empty_command_view_.IsValid());
- }
-
- void ListDependencies(ModuleList* list) const {}
- void Start() override {
- InitEmptyCommand();
- RegisterEventHandler(
- EventCode::COMMAND_COMPLETE, GetHandler()->BindOn(this, &TestHciLayer::CommandCompleteCallback));
- RegisterEventHandler(EventCode::COMMAND_STATUS, GetHandler()->BindOn(this, &TestHciLayer::CommandStatusCallback));
- }
- void Stop() override {
- UnregisterEventHandler(EventCode::COMMAND_STATUS);
- UnregisterEventHandler(EventCode::COMMAND_COMPLETE);
- }
-
- size_t CommandQueueSize() const {
- return command_queue_.size();
- }
-
- private:
- std::map<EventCode, common::ContextualCallback<void(EventView)>> registered_events_;
- std::map<SubeventCode, common::ContextualCallback<void(LeMetaEventView)>> registered_le_events_;
- std::list<common::ContextualOnceCallback<void(CommandCompleteView)>> command_complete_callbacks;
- std::list<common::ContextualOnceCallback<void(CommandStatusView)>> command_status_callbacks;
- std::queue<std::unique_ptr<CommandBuilder>> command_queue_;
- std::unique_ptr<std::promise<void>> command_promise_;
- std::future<void> command_future_;
- mutable std::mutex mutex_;
- uint16_t command_count_ = 0;
- CommandView empty_command_view_ =
- CommandView::Create(PacketView<kLittleEndian>(std::make_shared<std::vector<uint8_t>>()));
+ bool support_ble_extended_advertising_ = false;
};
class TestLeAddressManager : public LeAddressManager {
@@ -438,11 +290,7 @@
}
void sync_client_handler() {
- std::promise<void> promise;
- auto future = promise.get_future();
- client_handler_->Post(common::BindOnce(&std::promise<void>::set_value, common::Unretained(&promise)));
- auto future_status = future.wait_for(std::chrono::seconds(1));
- ASSERT_EQ(future_status, std::future_status::ready);
+ ASSERT(thread_.GetReactor()->WaitForIdle(std::chrono::seconds(2)));
}
TestModuleRegistry fake_registry_;
@@ -466,10 +314,13 @@
start_le_scanning_manager();
ASSERT_TRUE(fake_registry_.IsStarted(&HciLayer::Factory));
- test_hci_layer_->SetCommandFuture();
ASSERT_EQ(OpCode::LE_ADV_FILTER, test_hci_layer_->GetCommand().GetOpCode());
- ASSERT_EQ(0UL, test_hci_layer_->CommandQueueSize());
test_hci_layer_->IncomingEvent(LeAdvFilterReadExtendedFeaturesCompleteBuilder::Create(1, ErrorCode::SUCCESS, 0x01));
+
+ // Get the command a second time as the configure_scan is called twice in le_scanning_manager.cc
+ // Fixed on aosp/2242078 but not present on older branches
+ EXPECT_EQ(OpCode::LE_EXTENDED_SCAN_PARAMS, test_hci_layer_->GetCommand().GetOpCode());
+ test_hci_layer_->IncomingEvent(LeExtendedScanParamsCompleteBuilder::Create(uint8_t{1}, ErrorCode::SUCCESS));
}
void TearDown() override {
@@ -483,7 +334,12 @@
LeScanningManagerTest::SetUp();
test_controller_->AddSupported(OpCode::LE_SET_EXTENDED_SCAN_PARAMETERS);
test_controller_->AddSupported(OpCode::LE_SET_EXTENDED_SCAN_ENABLE);
+ test_controller_->SetBleExtendedAdvertisingSupport(true);
start_le_scanning_manager();
+ // Get the command a second time as the configure_scan is called twice in le_scanning_manager.cc
+ // Fixed on aosp/2242078 but not present on older branches
+ EXPECT_EQ(OpCode::LE_SET_EXTENDED_SCAN_PARAMETERS, test_hci_layer_->GetCommand().GetOpCode());
+ test_hci_layer_->IncomingEvent(LeSetExtendedScanParametersCompleteBuilder::Create(uint8_t{1}, ErrorCode::SUCCESS));
}
};
@@ -492,12 +348,17 @@
TEST_F(LeScanningManagerTest, start_scan_test) {
start_le_scanning_manager();
- test_hci_layer_->SetCommandFuture(2);
+ // Get the command a second time as the configure_scan is called twice in le_scanning_manager.cc
+ // Fixed on aosp/2242078 but not present on older branches
+ EXPECT_EQ(OpCode::LE_SET_SCAN_PARAMETERS, test_hci_layer_->GetCommand().GetOpCode());
+ test_hci_layer_->IncomingEvent(LeSetScanParametersCompleteBuilder::Create(uint8_t{1}, ErrorCode::SUCCESS));
+
// Enable scan
le_scanning_manager->Scan(true);
- ASSERT_EQ(OpCode::LE_SET_SCAN_PARAMETERS, test_hci_layer_->GetCommand().GetOpCode());
+ EXPECT_EQ(OpCode::LE_SET_SCAN_PARAMETERS, test_hci_layer_->GetCommand().GetOpCode());
test_hci_layer_->IncomingEvent(LeSetScanParametersCompleteBuilder::Create(uint8_t{1}, ErrorCode::SUCCESS));
- ASSERT_EQ(OpCode::LE_SET_SCAN_ENABLE, test_hci_layer_->GetCommand().GetOpCode());
+
+ EXPECT_EQ(OpCode::LE_SET_SCAN_ENABLE, test_hci_layer_->GetCommand().GetOpCode());
test_hci_layer_->IncomingEvent(LeSetScanEnableCompleteBuilder::Create(uint8_t{1}, ErrorCode::SUCCESS));
LeAdvertisingResponse report = make_advertising_report();
@@ -515,7 +376,7 @@
TEST_F(LeScanningManagerTest, scan_filter_add_ad_type_not_supported_test) {
start_le_scanning_manager();
ASSERT_TRUE(fake_registry_.IsStarted(&HciLayer::Factory));
- test_hci_layer_->SetCommandFuture();
+
std::vector<AdvertisingPacketContentFilterCommand> filters = {};
filters.push_back(make_filter(hci::ApcfFilterType::AD_TYPE));
le_scanning_manager->ScanFilterAdd(0x01, filters);
@@ -524,7 +385,6 @@
TEST_F(LeScanningManagerAndroidHciTest, startup_teardown) {}
TEST_F(LeScanningManagerAndroidHciTest, start_scan_test) {
- test_hci_layer_->SetCommandFuture(2);
// Enable scan
le_scanning_manager->Scan(true);
ASSERT_EQ(OpCode::LE_EXTENDED_SCAN_PARAMS, test_hci_layer_->GetCommand().GetOpCode());
@@ -545,14 +405,16 @@
TEST_F(LeScanningManagerAndroidHciTest, scan_filter_enable_test) {
le_scanning_manager->ScanFilterEnable(true);
+ sync_client_handler();
EXPECT_CALL(mock_callbacks_, OnFilterEnable);
test_hci_layer_->IncomingEvent(
LeAdvFilterEnableCompleteBuilder::Create(uint8_t{1}, ErrorCode::SUCCESS, Enable::ENABLED));
+ sync_client_handler();
}
TEST_F(LeScanningManagerAndroidHciTest, scan_filter_parameter_test) {
- test_hci_layer_->SetCommandFuture();
+
AdvertisingFilterParameter advertising_filter_parameter{};
advertising_filter_parameter.delivery_mode = DeliveryMode::IMMEDIATE;
le_scanning_manager->ScanFilterParameterSetup(ApcfAction::ADD, 0x01, advertising_filter_parameter);
@@ -566,10 +428,11 @@
EXPECT_CALL(mock_callbacks_, OnFilterParamSetup);
test_hci_layer_->IncomingEvent(
LeAdvFilterSetFilteringParametersCompleteBuilder::Create(uint8_t{1}, ErrorCode::SUCCESS, ApcfAction::ADD, 0x0a));
+ sync_client_handler();
}
TEST_F(LeScanningManagerAndroidHciTest, scan_filter_add_broadcaster_address_test) {
- test_hci_layer_->SetCommandFuture();
+
std::vector<AdvertisingPacketContentFilterCommand> filters = {};
filters.push_back(make_filter(ApcfFilterType::BROADCASTER_ADDRESS));
le_scanning_manager->ScanFilterAdd(0x01, filters);
@@ -586,7 +449,7 @@
}
TEST_F(LeScanningManagerAndroidHciTest, scan_filter_add_service_uuid_test) {
- test_hci_layer_->SetCommandFuture();
+
std::vector<AdvertisingPacketContentFilterCommand> filters = {};
filters.push_back(make_filter(ApcfFilterType::SERVICE_UUID));
le_scanning_manager->ScanFilterAdd(0x01, filters);
@@ -603,7 +466,7 @@
}
TEST_F(LeScanningManagerAndroidHciTest, scan_filter_add_local_name_test) {
- test_hci_layer_->SetCommandFuture();
+
std::vector<AdvertisingPacketContentFilterCommand> filters = {};
filters.push_back(make_filter(ApcfFilterType::LOCAL_NAME));
le_scanning_manager->ScanFilterAdd(0x01, filters);
@@ -620,7 +483,7 @@
}
TEST_F(LeScanningManagerAndroidHciTest, scan_filter_add_manufacturer_data_test) {
- test_hci_layer_->SetCommandFuture();
+
std::vector<AdvertisingPacketContentFilterCommand> filters = {};
filters.push_back(make_filter(ApcfFilterType::MANUFACTURER_DATA));
le_scanning_manager->ScanFilterAdd(0x01, filters);
@@ -637,7 +500,7 @@
}
TEST_F(LeScanningManagerAndroidHciTest, scan_filter_add_service_data_test) {
- test_hci_layer_->SetCommandFuture();
+
std::vector<AdvertisingPacketContentFilterCommand> filters = {};
filters.push_back(make_filter(hci::ApcfFilterType::SERVICE_DATA));
le_scanning_manager->ScanFilterAdd(0x01, filters);
@@ -680,20 +543,20 @@
LeBatchScanSetStorageParametersCompleteBuilder::Create(uint8_t{1}, ErrorCode::SUCCESS));
// Enable batch scan
- test_hci_layer_->SetCommandFuture();
+
le_scanning_manager->BatchScanEnable(BatchScanMode::FULL, 2400, 2400, BatchScanDiscardRule::OLDEST);
ASSERT_EQ(OpCode::LE_BATCH_SCAN, test_hci_layer_->GetCommand().GetOpCode());
test_hci_layer_->IncomingEvent(LeBatchScanEnableCompleteBuilder::Create(uint8_t{1}, ErrorCode::SUCCESS));
// Read batch scan data
- test_hci_layer_->SetCommandFuture();
+
le_scanning_manager->BatchScanReadReport(0x01, BatchScanMode::FULL);
ASSERT_EQ(OpCode::LE_BATCH_SCAN, test_hci_layer_->GetCommand().GetOpCode());
// We will send read command while num_of_record != 0
std::vector<uint8_t> raw_data = {0x5c, 0x1f, 0xa2, 0xc3, 0x63, 0x5d, 0x01, 0xf5, 0xb3, 0x5e, 0x00, 0x0c, 0x02,
0x01, 0x02, 0x05, 0x09, 0x6d, 0x76, 0x38, 0x76, 0x02, 0x0a, 0xf5, 0x00};
- test_hci_layer_->SetCommandFuture();
+
test_hci_layer_->IncomingEvent(LeBatchScanReadResultParametersCompleteRawBuilder::Create(
uint8_t{1}, ErrorCode::SUCCESS, BatchScanDataRead::FULL_MODE_DATA, 1, raw_data));
ASSERT_EQ(OpCode::LE_BATCH_SCAN, test_hci_layer_->GetCommand().GetOpCode());
@@ -708,7 +571,6 @@
TEST_F(LeScanningManagerExtendedTest, start_scan_test) {
// Enable scan
- test_hci_layer_->SetCommandFuture(2);
le_scanning_manager->Scan(true);
ASSERT_EQ(OpCode::LE_SET_EXTENDED_SCAN_PARAMETERS, test_hci_layer_->GetCommand().GetOpCode());
test_hci_layer_->IncomingEvent(LeSetExtendedScanParametersCompleteBuilder::Create(uint8_t{1}, ErrorCode::SUCCESS));
@@ -742,7 +604,6 @@
test_le_address_manager->ignore_unregister_for_testing = true;
// Register LeAddressManager
- test_hci_layer_->SetCommandFuture(2);
le_scanning_manager->Scan(true);
ASSERT_EQ(OpCode::LE_SET_EXTENDED_SCAN_PARAMETERS, test_hci_layer_->GetCommand().GetOpCode());
test_hci_layer_->IncomingEvent(LeSetExtendedScanParametersCompleteBuilder::Create(uint8_t{1}, ErrorCode::SUCCESS));
@@ -751,7 +612,6 @@
sync_client_handler();
// Unregister LeAddressManager
- test_hci_layer_->SetCommandFuture(1);
le_scanning_manager->Scan(false);
ASSERT_EQ(OpCode::LE_SET_EXTENDED_SCAN_ENABLE, test_hci_layer_->GetCommand().GetOpCode());
test_hci_layer_->IncomingEvent(LeSetExtendedScanEnableCompleteBuilder::Create(uint8_t{1}, ErrorCode::SUCCESS));
@@ -768,7 +628,6 @@
TEST_F(LeScanningManagerExtendedTest, drop_insignificant_bytes_test) {
// Enable scan
- test_hci_layer_->SetCommandFuture(2);
le_scanning_manager->Scan(true);
ASSERT_EQ(OpCode::LE_SET_EXTENDED_SCAN_PARAMETERS, test_hci_layer_->GetCommand().GetOpCode());
test_hci_layer_->IncomingEvent(LeSetExtendedScanParametersCompleteBuilder::Create(uint8_t{1}, ErrorCode::SUCCESS));
diff --git a/system/gd/os/Android.bp b/system/gd/os/Android.bp
index 7431f15..292da06 100644
--- a/system/gd/os/Android.bp
+++ b/system/gd/os/Android.bp
@@ -17,6 +17,7 @@
filegroup {
name: "BluetoothOsSources_android",
srcs: [
+ "system_properties_common.cc",
"android/metrics.cc",
"android/parameter_provider.cc",
"android/system_properties.cc",
@@ -35,6 +36,7 @@
filegroup {
name: "BluetoothOsSources_host",
srcs: [
+ "system_properties_common.cc",
"host/metrics.cc",
"host/parameter_provider.cc",
"host/system_properties.cc",
diff --git a/system/gd/os/BUILD.gn b/system/gd/os/BUILD.gn
index 48337e1..7204ccd 100644
--- a/system/gd/os/BUILD.gn
+++ b/system/gd/os/BUILD.gn
@@ -18,6 +18,7 @@
"linux/parameter_provider.cc",
"linux/system_properties.cc",
"linux/wakelock_native.cc",
+ "system_properties_common.cc",
"syslog.cc",
]
diff --git a/system/gd/os/android/metrics.cc b/system/gd/os/android/metrics.cc
index db42fc5..400e925 100644
--- a/system/gd/os/android/metrics.cc
+++ b/system/gd/os/android/metrics.cc
@@ -236,7 +236,7 @@
}
void LogMetricSmpPairingEvent(
- const Address& address, uint8_t smp_cmd, android::bluetooth::DirectionEnum direction, uint8_t smp_fail_reason) {
+ const Address& address, uint16_t smp_cmd, android::bluetooth::DirectionEnum direction, uint16_t smp_fail_reason) {
int metric_id = 0;
if (!address.IsEmpty()) {
metric_id = MetricIdManager::GetInstance().AllocateId(address);
@@ -443,7 +443,12 @@
uint8_t hci_version,
uint32_t hci_revision) {
int ret = stats_write(
- BLUETOOTH_LOCAL_VERSIONS_REPORTED, lmp_manufacturer_name, lmp_version, lmp_subversion, hci_version, hci_revision);
+ BLUETOOTH_LOCAL_VERSIONS_REPORTED,
+ static_cast<int32_t>(lmp_manufacturer_name),
+ static_cast<int32_t>(lmp_version),
+ static_cast<int32_t>(lmp_subversion),
+ static_cast<int32_t>(hci_version),
+ static_cast<int32_t>(hci_revision));
if (ret < 0) {
LOG_WARN(
"Failed for LogMetricBluetoothLocalVersions, "
diff --git a/system/gd/os/android/wakelock_native_test.cc b/system/gd/os/android/wakelock_native_test.cc
index 66be722..36f8af8 100644
--- a/system/gd/os/android/wakelock_native_test.cc
+++ b/system/gd/os/android/wakelock_native_test.cc
@@ -53,8 +53,9 @@
static void FulfilPromise(std::unique_ptr<std::promise<void>>& promise) {
std::lock_guard<std::recursive_mutex> lock_guard(mutex);
if (promise != nullptr) {
- promise->set_value();
- promise = nullptr;
+ std::promise<void>* prom = promise.release();
+ prom->set_value();
+ delete prom;
}
}
diff --git a/system/gd/os/handler_unittest.cc b/system/gd/os/handler_unittest.cc
index b8c580f..04f6347 100644
--- a/system/gd/os/handler_unittest.cc
+++ b/system/gd/os/handler_unittest.cc
@@ -70,19 +70,27 @@
auto closure_started_future = closure_started.get_future();
std::promise<void> closure_can_continue;
auto can_continue_future = closure_can_continue.get_future();
+ std::promise<void> closure_finished;
+ auto closure_finished_future = closure_finished.get_future();
handler_->Post(common::BindOnce(
- [](int* val, std::promise<void> closure_started, std::future<void> can_continue_future) {
+ [](int* val,
+ std::promise<void> closure_started,
+ std::future<void> can_continue_future,
+ std::promise<void> closure_finished) {
closure_started.set_value();
*val = *val + 1;
can_continue_future.wait();
+ closure_finished.set_value();
},
common::Unretained(&val),
std::move(closure_started),
- std::move(can_continue_future)));
+ std::move(can_continue_future),
+ std::move(closure_finished)));
handler_->Post(common::BindOnce([]() { ASSERT_TRUE(false); }));
closure_started_future.wait();
handler_->Clear();
closure_can_continue.set_value();
+ closure_finished_future.wait();
ASSERT_EQ(val, 1);
}
diff --git a/system/gd/os/host/metrics.cc b/system/gd/os/host/metrics.cc
index 955d22e..2e85e73 100644
--- a/system/gd/os/host/metrics.cc
+++ b/system/gd/os/host/metrics.cc
@@ -96,7 +96,7 @@
const char* attribute_value) {}
void LogMetricSmpPairingEvent(
- const Address& address, uint8_t smp_cmd, android::bluetooth::DirectionEnum direction, uint8_t smp_fail_reason) {}
+ const Address& address, uint16_t smp_cmd, android::bluetooth::DirectionEnum direction, uint16_t smp_fail_reason) {}
void LogMetricA2dpPlaybackEvent(const Address& address, int playback_state, int audio_coding_mode) {}
diff --git a/system/gd/os/linux/metrics.cc b/system/gd/os/linux/metrics.cc
index 65fb181..3941f77 100644
--- a/system/gd/os/linux/metrics.cc
+++ b/system/gd/os/linux/metrics.cc
@@ -96,7 +96,7 @@
const char* attribute_value) {}
void LogMetricSmpPairingEvent(
- const Address& address, uint8_t smp_cmd, android::bluetooth::DirectionEnum direction, uint8_t smp_fail_reason) {}
+ const Address& address, uint16_t smp_cmd, android::bluetooth::DirectionEnum direction, uint16_t smp_fail_reason) {}
void LogMetricA2dpPlaybackEvent(const Address& address, int playback_state, int audio_coding_mode) {}
diff --git a/system/gd/os/linux_generic/queue_unittest.cc b/system/gd/os/linux_generic/queue_unittest.cc
index 3739735..706fcd2 100644
--- a/system/gd/os/linux_generic/queue_unittest.cc
+++ b/system/gd/os/linux_generic/queue_unittest.cc
@@ -19,6 +19,7 @@
#include <sys/eventfd.h>
#include <atomic>
+#include <chrono>
#include <future>
#include <unordered_map>
@@ -26,6 +27,8 @@
#include "gtest/gtest.h"
#include "os/reactor.h"
+using namespace std::chrono_literals;
+
namespace bluetooth {
namespace os {
namespace {
@@ -60,6 +63,11 @@
Handler* enqueue_handler_;
Thread* dequeue_thread_;
Handler* dequeue_handler_;
+
+ void sync_enqueue_handler() {
+ ASSERT(enqueue_thread_ != nullptr);
+ ASSERT(enqueue_thread_->GetReactor()->WaitForIdle(2s));
+ }
};
class TestEnqueueEnd {
@@ -96,11 +104,12 @@
queue_->UnregisterEnqueue();
}
- auto pair = promise_map_->find(buffer_.size());
- if (pair != promise_map_->end()) {
- pair->second.set_value(pair->first);
- promise_map_->erase(pair->first);
+ auto key = buffer_.size();
+ auto node = promise_map_->extract(key);
+ if (node) {
+ node.mapped().set_value(key);
}
+
return data;
}
@@ -161,10 +170,10 @@
queue_->UnregisterDequeue();
}
- auto pair = promise_map_->find(buffer_.size());
- if (pair != promise_map_->end()) {
- pair->second.set_value(pair->first);
- promise_map_->erase(pair->first);
+ auto key = buffer_.size();
+ auto node = promise_map_->extract(key);
+ if (node) {
+ node.mapped().set_value(key);
}
}
@@ -337,6 +346,7 @@
test_enqueue_end.RegisterEnqueue(&enqueue_promise_map);
enqueue_future.wait();
EXPECT_EQ(enqueue_future.get(), 0);
+ sync_enqueue_handler();
}
// Enqueue end level : 1
diff --git a/system/gd/os/metrics.h b/system/gd/os/metrics.h
index 339a2cf..cb35d68 100644
--- a/system/gd/os/metrics.h
+++ b/system/gd/os/metrics.h
@@ -166,7 +166,10 @@
* @param smp_fail_reason SMP pairing failure reason code from SMP spec
*/
void LogMetricSmpPairingEvent(
- const hci::Address& address, uint8_t smp_cmd, android::bluetooth::DirectionEnum direction, uint8_t smp_fail_reason);
+ const hci::Address& address,
+ uint16_t smp_cmd,
+ android::bluetooth::DirectionEnum direction,
+ uint16_t smp_fail_reason);
/**
* Logs there is an event related Bluetooth classic pairing
diff --git a/system/gd/os/system_properties.h b/system/gd/os/system_properties.h
index f5a82b8..42ea4e7 100644
--- a/system/gd/os/system_properties.h
+++ b/system/gd/os/system_properties.h
@@ -26,6 +26,21 @@
// or if the platform does not support system property
std::optional<std::string> GetSystemProperty(const std::string& property);
+// Get |property| keyed system property as uint32_t from supported platform, return |default_value| if the property
+// does not exist or if the platform does not support system property
+uint32_t GetSystemPropertyUint32(const std::string& property, uint32_t default_value);
+
+// Get |property| keyed system property as uint32_t from supported platform, return |default_value|
+// if the property does not exist or if the platform does not support system property if property is
+// found it will call stoul with |base|
+uint32_t GetSystemPropertyUint32Base(
+ const std::string& property, uint32_t default_value, int base = 0);
+
+// Get |property| keyed property as bool from supported platform, return
+// |default_value| if the property does not exist or if the platform
+// does not support system property
+bool GetSystemPropertyBool(const std::string& property, bool default_value);
+
// Set |property| keyed system property to |value|, return true if the set was successful and false if the set failed
// Replace existing value if property already exists
bool SetSystemProperty(const std::string& property, const std::string& value);
diff --git a/system/gd/os/system_properties_common.cc b/system/gd/os/system_properties_common.cc
new file mode 100644
index 0000000..f59560c
--- /dev/null
+++ b/system/gd/os/system_properties_common.cc
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <string>
+
+#include "common/strings.h"
+#include "os/system_properties.h"
+
+namespace bluetooth {
+namespace os {
+
+uint32_t GetSystemPropertyUint32(const std::string& property, uint32_t default_value) {
+ return GetSystemPropertyUint32Base(property, default_value, 10);
+}
+
+uint32_t GetSystemPropertyUint32Base(
+ const std::string& property, uint32_t default_value, int base) {
+ std::optional<std::string> result = GetSystemProperty(property);
+ if (result.has_value()) {
+ return static_cast<uint32_t>(std::stoul(*result, nullptr, base));
+ }
+ return default_value;
+}
+
+bool GetSystemPropertyBool(const std::string& property, bool default_value) {
+ std::optional<std::string> result = GetSystemProperty(property);
+ if (result.has_value()) {
+ std::string trimmed_val = common::StringTrim(result.value());
+ if (trimmed_val == "true" || trimmed_val == "1") {
+ return true;
+ }
+ if (trimmed_val == "false" || trimmed_val == "0") {
+ return false;
+ }
+ }
+ return default_value;
+}
+
+} // namespace os
+} // namespace bluetooth
diff --git a/system/gd/rust/common/src/init_flags.rs b/system/gd/rust/common/src/init_flags.rs
index 21e0c41..2beb2b5 100644
--- a/system/gd/rust/common/src/init_flags.rs
+++ b/system/gd/rust/common/src/init_flags.rs
@@ -207,7 +207,10 @@
flags: {
asynchronously_start_l2cap_coc = true,
btaa_hci = true,
+ bta_dm_clear_conn_id_on_client_close = true,
btm_dm_flush_discovery_queue_on_search_cancel,
+ clear_hidd_interrupt_cid_on_disconnect = true,
+ delay_hidh_cleanup_until_hidh_ready_start = true,
finite_att_timeout = true,
gatt_robust_caching_client = true,
gatt_robust_caching_server,
diff --git a/system/gd/rust/shim/src/init_flags.rs b/system/gd/rust/shim/src/init_flags.rs
index 071d658..5c1e29d 100644
--- a/system/gd/rust/shim/src/init_flags.rs
+++ b/system/gd/rust/shim/src/init_flags.rs
@@ -6,7 +6,10 @@
fn asynchronously_start_l2cap_coc_is_enabled() -> bool;
fn btaa_hci_is_enabled() -> bool;
+ fn bta_dm_clear_conn_id_on_client_close_is_enabled() -> bool;
fn btm_dm_flush_discovery_queue_on_search_cancel_is_enabled() -> bool;
+ fn delay_hidh_cleanup_until_hidh_ready_start_is_enabled() -> bool;
+ fn clear_hidd_interrupt_cid_on_disconnect_is_enabled() -> bool;
fn finite_att_timeout_is_enabled() -> bool;
fn gatt_robust_caching_client_is_enabled() -> bool;
fn gatt_robust_caching_server_is_enabled() -> bool;
diff --git a/system/gd/stack_manager_unittest.cc b/system/gd/stack_manager_unittest.cc
index 824675e..51645ea 100644
--- a/system/gd/stack_manager_unittest.cc
+++ b/system/gd/stack_manager_unittest.cc
@@ -22,7 +22,7 @@
namespace bluetooth {
namespace {
-TEST(StackManagerTest, start_and_shutdown_no_module) {
+TEST(StackManagerTest, DISABLED_start_and_shutdown_no_module) {
StackManager stack_manager;
ModuleList module_list;
os::Thread thread{"test_thread", os::Thread::Priority::NORMAL};
@@ -45,7 +45,7 @@
const ModuleFactory TestModuleNoDependency::Factory = ModuleFactory([]() { return new TestModuleNoDependency(); });
-TEST(StackManagerTest, get_module_instance) {
+TEST(StackManagerTest, DISABLED_get_module_instance) {
StackManager stack_manager;
ModuleList module_list;
module_list.add<TestModuleNoDependency>();
diff --git a/system/hci/Android.bp b/system/hci/Android.bp
index d9f9542..1553c25 100644
--- a/system/hci/Android.bp
+++ b/system/hci/Android.bp
@@ -91,28 +91,6 @@
],
}
-// HCI native unit tests for target
-cc_test {
- name: "net_test_hci_native",
- test_suites: ["device-tests"],
- defaults: [
- "fluoride_unit_test_defaults",
- "mts_defaults",
- ],
- local_include_dirs: [
- "include",
- ],
- include_dirs: [
- "packages/modules/Bluetooth/system",
- "packages/modules/Bluetooth/system/gd",
- "packages/modules/Bluetooth/system/stack/include",
- ],
- srcs: [
- "test/hci_layer_test.cc",
- "test/other_stack_stub.cc",
- ],
-}
-
cc_test {
name: "net_test_hci_fragmenter_native",
test_suites: ["device-tests"],
diff --git a/system/hci/test/hci_layer_test.cc b/system/hci/test/hci_layer_test.cc
deleted file mode 100644
index e69de29..0000000
--- a/system/hci/test/hci_layer_test.cc
+++ /dev/null
diff --git a/system/hci/test/other_stack_stub.cc b/system/hci/test/other_stack_stub.cc
deleted file mode 100644
index e69de29..0000000
--- a/system/hci/test/other_stack_stub.cc
+++ /dev/null
diff --git a/system/include/hardware/bt_hf_client.h b/system/include/hardware/bt_hf_client.h
index 7263805..bf8b1bb 100644
--- a/system/include/hardware/bt_hf_client.h
+++ b/system/include/hardware/bt_hf_client.h
@@ -392,6 +392,9 @@
/** Send AT Command. */
bt_status_t (*send_at_cmd)(const RawAddress* bd_addr, int cmd, int val1,
int val2, const char* arg);
+
+ /** Send hfp audio policy to remote */
+ bt_status_t (*send_android_at)(const RawAddress* bd_addr, const char* arg);
} bthf_client_interface_t;
__END_DECLS
diff --git a/system/internal_include/bt_target.h b/system/internal_include/bt_target.h
index 98787a3..0aab570 100644
--- a/system/internal_include/bt_target.h
+++ b/system/internal_include/bt_target.h
@@ -75,10 +75,6 @@
#define BTA_HH_ROLE BTA_CENTRAL_ROLE_PREF
#endif
-#ifndef BTA_DISABLE_DELAY
-#define BTA_DISABLE_DELAY 200 /* in milliseconds */
-#endif
-
#ifndef AVDT_VERSION
#define AVDT_VERSION 0x0103
#endif
@@ -818,10 +814,6 @@
#define PAN_INCLUDED TRUE
#endif
-#ifndef PAN_NAP_DISABLED
-#define PAN_NAP_DISABLED FALSE
-#endif
-
#ifndef PANU_DISABLED
#define PANU_DISABLED FALSE
#endif
@@ -974,10 +966,6 @@
*
*****************************************************************************/
-#ifndef AVRC_ADV_CTRL_INCLUDED
-#define AVRC_ADV_CTRL_INCLUDED TRUE
-#endif
-
#ifndef DUMP_PCM_DATA
#define DUMP_PCM_DATA FALSE
#endif
@@ -1026,12 +1014,4 @@
#include "bt_trace.h"
-#ifndef BTM_DELAY_AUTH_MS
-#define BTM_DELAY_AUTH_MS 0
-#endif
-
-#ifndef BTM_DISABLE_CONCURRENT_PEER_AUTH
-#define BTM_DISABLE_CONCURRENT_PEER_AUTH FALSE
-#endif
-
#endif /* BT_TARGET_H */
diff --git a/system/main/shim/metrics_api.cc b/system/main/shim/metrics_api.cc
index 99832be..82ad3c1 100644
--- a/system/main/shim/metrics_api.cc
+++ b/system/main/shim/metrics_api.cc
@@ -89,9 +89,9 @@
transmit_power_level);
}
-void LogMetricSmpPairingEvent(const RawAddress& raw_address, uint8_t smp_cmd,
+void LogMetricSmpPairingEvent(const RawAddress& raw_address, uint16_t smp_cmd,
android::bluetooth::DirectionEnum direction,
- uint8_t smp_fail_reason) {
+ uint16_t smp_fail_reason) {
Address address = bluetooth::ToGdAddress(raw_address);
bluetooth::os::LogMetricSmpPairingEvent(address, smp_cmd, direction,
smp_fail_reason);
diff --git a/system/main/shim/metrics_api.h b/system/main/shim/metrics_api.h
index ce13876..f838908 100644
--- a/system/main/shim/metrics_api.h
+++ b/system/main/shim/metrics_api.h
@@ -134,9 +134,9 @@
* @param direction direction of this SMP command
* @param smp_fail_reason SMP pairing failure reason code from SMP spec
*/
-void LogMetricSmpPairingEvent(const RawAddress& address, uint8_t smp_cmd,
+void LogMetricSmpPairingEvent(const RawAddress& address, uint16_t smp_cmd,
android::bluetooth::DirectionEnum direction,
- uint8_t smp_fail_reason);
+ uint16_t smp_fail_reason);
/**
* Logs there is an event related Bluetooth classic pairing
diff --git a/system/packet/avrcp/get_element_attributes_packet.cc b/system/packet/avrcp/get_element_attributes_packet.cc
index 8634ce0..f08a698 100644
--- a/system/packet/avrcp/get_element_attributes_packet.cc
+++ b/system/packet/avrcp/get_element_attributes_packet.cc
@@ -90,7 +90,7 @@
return builder;
}
-bool GetElementAttributesResponseBuilder::AddAttributeEntry(
+size_t GetElementAttributesResponseBuilder::AddAttributeEntry(
AttributeEntry entry) {
CHECK_LT(entries_.size(), size_t(0xFF))
<< __func__ << ": attribute entry overflow";
@@ -101,15 +101,15 @@
}
if (entry.empty()) {
- return false;
+ return 0;
}
entries_.insert(entry);
- return true;
+ return entry.size();
}
-bool GetElementAttributesResponseBuilder::AddAttributeEntry(Attribute attribute,
- std::string value) {
+size_t GetElementAttributesResponseBuilder::AddAttributeEntry(
+ Attribute attribute, const std::string& value) {
return AddAttributeEntry(AttributeEntry(attribute, value));
}
@@ -120,7 +120,7 @@
attr_list_size += attribute_entry.size();
}
- return VendorPacket::kMinSize() + 1 + attr_list_size;
+ return kHeaderSize() + attr_list_size;
}
bool GetElementAttributesResponseBuilder::Serialize(
diff --git a/system/packet/avrcp/get_element_attributes_packet.h b/system/packet/avrcp/get_element_attributes_packet.h
index e60844c..b1d4c61 100644
--- a/system/packet/avrcp/get_element_attributes_packet.h
+++ b/system/packet/avrcp/get_element_attributes_packet.h
@@ -58,15 +58,21 @@
using VendorPacket::VendorPacket;
};
+template <class Builder>
+class AttributesResponseBuilderTestUser;
+
class GetElementAttributesResponseBuilder : public VendorPacketBuilder {
public:
virtual ~GetElementAttributesResponseBuilder() = default;
+ using Builder = std::unique_ptr<GetElementAttributesResponseBuilder>;
+ static Builder MakeBuilder(size_t mtu);
- static std::unique_ptr<GetElementAttributesResponseBuilder> MakeBuilder(
- size_t mtu);
+ size_t AddAttributeEntry(AttributeEntry entry);
+ size_t AddAttributeEntry(Attribute attribute, const std::string& value);
- bool AddAttributeEntry(AttributeEntry entry);
- bool AddAttributeEntry(Attribute attribute, std::string value);
+ virtual void clear() { entries_.clear(); }
+
+ static constexpr size_t kHeaderSize() { return VendorPacket::kMinSize() + 1; }
virtual size_t size() const override;
virtual bool Serialize(
@@ -75,6 +81,8 @@
private:
std::set<AttributeEntry> entries_;
size_t mtu_;
+ friend class AttributesResponseBuilderTestUser<
+ GetElementAttributesResponseBuilder>;
GetElementAttributesResponseBuilder(size_t mtu)
: VendorPacketBuilder(CType::STABLE, CommandPdu::GET_ELEMENT_ATTRIBUTES,
@@ -83,4 +91,4 @@
};
} // namespace avrcp
-} // namespace bluetooth
\ No newline at end of file
+} // namespace bluetooth
diff --git a/system/packet/avrcp/get_item_attributes.cc b/system/packet/avrcp/get_item_attributes.cc
index fb2ba87..0c7afe4 100644
--- a/system/packet/avrcp/get_item_attributes.cc
+++ b/system/packet/avrcp/get_item_attributes.cc
@@ -27,7 +27,8 @@
return builder;
}
-bool GetItemAttributesResponseBuilder::AddAttributeEntry(AttributeEntry entry) {
+size_t GetItemAttributesResponseBuilder::AddAttributeEntry(
+ AttributeEntry entry) {
CHECK(entries_.size() < 0xFF);
size_t remaining_space = mtu_ - size();
@@ -36,24 +37,22 @@
}
if (entry.empty()) {
- return false;
+ return 0;
}
entries_.insert(entry);
- return true;
+ return entry.size();
}
-bool GetItemAttributesResponseBuilder::AddAttributeEntry(Attribute attribute,
- std::string value) {
+size_t GetItemAttributesResponseBuilder::AddAttributeEntry(
+ Attribute attribute, const std::string& value) {
return AddAttributeEntry(AttributeEntry(attribute, value));
}
size_t GetItemAttributesResponseBuilder::size() const {
- size_t len = BrowsePacket::kMinSize();
- len += 1; // Status
- if (status_ != Status::NO_ERROR) return len;
+ size_t len = kHeaderSize();
+ if (status_ != Status::NO_ERROR) return kErrorHeaderSize();
- len += 1; // Number of attributes
for (const auto& entry : entries_) {
len += entry.size();
}
diff --git a/system/packet/avrcp/get_item_attributes.h b/system/packet/avrcp/get_item_attributes.h
index aa1db71..7811909 100644
--- a/system/packet/avrcp/get_item_attributes.h
+++ b/system/packet/avrcp/get_item_attributes.h
@@ -23,15 +23,32 @@
namespace bluetooth {
namespace avrcp {
+template <class Builder>
+class AttributesResponseBuilderTestUser;
+
class GetItemAttributesResponseBuilder : public BrowsePacketBuilder {
public:
virtual ~GetItemAttributesResponseBuilder() = default;
+ using Builder = std::unique_ptr<GetItemAttributesResponseBuilder>;
+ static Builder MakeBuilder(Status status, size_t mtu);
- static std::unique_ptr<GetItemAttributesResponseBuilder> MakeBuilder(
- Status status, size_t mtu);
+ size_t AddAttributeEntry(AttributeEntry entry);
+ size_t AddAttributeEntry(Attribute, const std::string&);
- bool AddAttributeEntry(AttributeEntry entry);
- bool AddAttributeEntry(Attribute, std::string);
+ virtual void clear() { entries_.clear(); }
+
+ static constexpr size_t kHeaderSize() {
+ size_t len = BrowsePacket::kMinSize();
+ len += 1; // Status
+ len += 1; // Number of attributes
+ return len;
+ }
+
+ static constexpr size_t kErrorHeaderSize() {
+ size_t len = BrowsePacket::kMinSize();
+ len += 1; // Status
+ return len;
+ }
virtual size_t size() const override;
virtual bool Serialize(
@@ -41,6 +58,8 @@
Status status_;
size_t mtu_;
std::set<AttributeEntry> entries_;
+ friend class AttributesResponseBuilderTestUser<
+ GetItemAttributesResponseBuilder>;
GetItemAttributesResponseBuilder(Status status, size_t mtu)
: BrowsePacketBuilder(BrowsePdu::GET_ITEM_ATTRIBUTES),
@@ -81,4 +100,4 @@
};
} // namespace avrcp
-} // namespace bluetooth
\ No newline at end of file
+} // namespace bluetooth
diff --git a/system/packet/tests/avrcp/get_element_attributes_packet_test.cc b/system/packet/tests/avrcp/get_element_attributes_packet_test.cc
index 8a05487..96ae1fa 100644
--- a/system/packet/tests/avrcp/get_element_attributes_packet_test.cc
+++ b/system/packet/tests/avrcp/get_element_attributes_packet_test.cc
@@ -103,6 +103,47 @@
ASSERT_EQ(test_packet->GetData(), get_elements_attributes_response_full);
}
+TEST(GetElementAttributesResponseBuilderTest, builderMtuTest) {
+ std::vector<AttributeEntry> test_data = {
+ {Attribute::TITLE, "Test Song 1"},
+ {Attribute::ARTIST_NAME, "Test Artist"},
+ {Attribute::ALBUM_NAME, "Test Album"},
+ {Attribute::TRACK_NUMBER, "1"},
+ {Attribute::TOTAL_NUMBER_OF_TRACKS, "2"},
+ {Attribute::GENRE, "Test Genre"},
+ {Attribute::PLAYING_TIME, "10 200"},
+ {Attribute::TITLE, "Test Song 2"},
+ {Attribute::ARTIST_NAME, "Test Artist"},
+ {Attribute::ALBUM_NAME, "Test Album"},
+ {Attribute::TRACK_NUMBER, "2"},
+ {Attribute::TOTAL_NUMBER_OF_TRACKS, "2"},
+ {Attribute::GENRE, "Test Genre"},
+ {Attribute::PLAYING_TIME, "1500"},
+ };
+
+ using Builder = GetElementAttributesResponseBuilder;
+ using Helper = FragmentationBuilderHelper<Builder>;
+ size_t mtu = size_t(-1);
+ Helper helper(mtu, [](size_t mtu) { return Builder::MakeBuilder(mtu); });
+
+ EXPECT_NO_FATAL_FAILURE(helper.runTest(test_data, mtu, false, false));
+
+ mtu = test_data[0].size() + Builder::kHeaderSize();
+ EXPECT_NO_FATAL_FAILURE(helper.runTest(test_data, mtu));
+
+ mtu = test_data[0].size() + test_data[1].size() + Builder::kHeaderSize();
+ EXPECT_NO_FATAL_FAILURE(helper.runTest(test_data, mtu));
+
+ mtu = test_data[0].size() + (Builder::kHeaderSize() * 2) + 1;
+ EXPECT_NO_FATAL_FAILURE(helper.runTest(test_data, mtu, true, false));
+
+ mtu = Builder::kHeaderSize() + AttributeEntry::kHeaderSize() + 1;
+ EXPECT_NO_FATAL_FAILURE(helper.runTest(test_data, mtu));
+
+ mtu = Builder::kHeaderSize() + AttributeEntry::kHeaderSize();
+ EXPECT_NO_FATAL_FAILURE(helper.runTest(test_data, mtu, false, false));
+}
+
TEST(GetElementAttributesResponseBuilderTest, truncateBuilderTest) {
auto attribute = AttributeEntry(Attribute::TITLE, "1234");
size_t truncated_size = VendorPacket::kMinSize();
@@ -130,4 +171,4 @@
}
} // namespace avrcp
-} // namespace bluetooth
\ No newline at end of file
+} // namespace bluetooth
diff --git a/system/packet/tests/avrcp/get_item_attributes_packet_test.cc b/system/packet/tests/avrcp/get_item_attributes_packet_test.cc
index 3d6f691..4f1da7b 100644
--- a/system/packet/tests/avrcp/get_item_attributes_packet_test.cc
+++ b/system/packet/tests/avrcp/get_item_attributes_packet_test.cc
@@ -126,5 +126,48 @@
ASSERT_FALSE(test_packet->IsValid());
}
+TEST(GetItemAttributesRequestTest, builderMtuTest) {
+ std::vector<AttributeEntry> test_data = {
+ {Attribute::TITLE, "Test Song 1"},
+ {Attribute::ARTIST_NAME, "Test Artist"},
+ {Attribute::ALBUM_NAME, "Test Album"},
+ {Attribute::TRACK_NUMBER, "1"},
+ {Attribute::TOTAL_NUMBER_OF_TRACKS, "2"},
+ {Attribute::GENRE, "Test Genre"},
+ {Attribute::PLAYING_TIME, "10 200"},
+ {Attribute::TITLE, "Test Song 2"},
+ {Attribute::ARTIST_NAME, "Test Artist"},
+ {Attribute::ALBUM_NAME, "Test Album"},
+ {Attribute::TRACK_NUMBER, "2"},
+ {Attribute::TOTAL_NUMBER_OF_TRACKS, "2"},
+ {Attribute::GENRE, "Test Genre"},
+ {Attribute::PLAYING_TIME, "1500"},
+ };
+
+ using Builder = GetItemAttributesResponseBuilder;
+ using Helper = FragmentationBuilderHelper<Builder>;
+ size_t mtu = size_t(-1);
+ Helper helper(mtu, [](size_t mtu) {
+ return Builder::MakeBuilder(Status::NO_ERROR, mtu);
+ });
+
+ EXPECT_NO_FATAL_FAILURE(helper.runTest(test_data, mtu, false, false));
+
+ mtu = test_data[0].size() + Builder::kHeaderSize();
+ EXPECT_NO_FATAL_FAILURE(helper.runTest(test_data, mtu));
+
+ mtu = test_data[0].size() + test_data[1].size() + Builder::kHeaderSize();
+ EXPECT_NO_FATAL_FAILURE(helper.runTest(test_data, mtu));
+
+ mtu = test_data[0].size() + (Builder::kHeaderSize() * 2) + 1;
+ EXPECT_NO_FATAL_FAILURE(helper.runTest(test_data, mtu, true, false));
+
+ mtu = Builder::kHeaderSize() + AttributeEntry::kHeaderSize() + 1;
+ EXPECT_NO_FATAL_FAILURE(helper.runTest(test_data, mtu));
+
+ mtu = Builder::kHeaderSize() + AttributeEntry::kHeaderSize();
+ EXPECT_NO_FATAL_FAILURE(helper.runTest(test_data, mtu, false, false));
+}
+
} // namespace avrcp
-} // namespace bluetooth
\ No newline at end of file
+} // namespace bluetooth
diff --git a/system/packet/tests/packet_test_helper.h b/system/packet/tests/packet_test_helper.h
index ad3db2c..e03280c 100644
--- a/system/packet/tests/packet_test_helper.h
+++ b/system/packet/tests/packet_test_helper.h
@@ -16,10 +16,11 @@
#pragma once
+#include <list>
#include <memory>
+#include "avrcp_common.h"
#include "packet.h"
-
namespace bluetooth {
// A helper templated class to access the protected members of Packet to make
@@ -63,4 +64,229 @@
}
};
-} // namespace bluetooth
\ No newline at end of file
+namespace avrcp {
+
+inline std::string to_string(const Attribute& a) {
+ switch (a) {
+ case Attribute::TITLE:
+ return "TITLE";
+ case Attribute::ARTIST_NAME:
+ return "ARTIST_NAME";
+ case Attribute::ALBUM_NAME:
+ return "ALBUM_NAME";
+ case Attribute::TRACK_NUMBER:
+ return "TRACK_NUMBER";
+ case Attribute::TOTAL_NUMBER_OF_TRACKS:
+ return "TOTAL_NUMBER_OF_TRACKS";
+ case Attribute::GENRE:
+ return "GENRE";
+ case Attribute::PLAYING_TIME:
+ return "PLAYING_TIME";
+ case Attribute::DEFAULT_COVER_ART:
+ return "DEFAULT_COVER_ART";
+ default:
+ return "UNKNOWN ATTRIBUTE";
+ };
+}
+
+inline std::string to_string(const AttributeEntry& entry) {
+ std::stringstream ss;
+ ss << to_string(entry.attribute()) << ": " << entry.value();
+ return ss.str();
+}
+
+template <class Container>
+std::string to_string(const Container& entries) {
+ std::stringstream ss;
+ for (const auto& el : entries) {
+ ss << to_string(el) << std::endl;
+ }
+ return ss.str();
+}
+
+inline bool operator==(const AttributeEntry& a, const AttributeEntry& b) {
+ return (a.attribute() == b.attribute()) && (a.value() == b.value());
+}
+
+inline bool operator!=(const AttributeEntry& a, const AttributeEntry& b) {
+ return !(a == b);
+}
+
+template <class AttributesResponseBuilder>
+class AttributesResponseBuilderTestUser {
+ public:
+ using Builder = AttributesResponseBuilder;
+ using Maker = std::function<typename Builder::Builder(size_t)>;
+
+ private:
+ Maker maker;
+ typename Builder::Builder _builder;
+ size_t _mtu;
+ size_t _current_size = 0;
+ size_t _entry_counter = 0;
+ std::set<AttributeEntry> _control_set;
+ std::list<AttributeEntry> _order_control;
+ std::list<AttributeEntry> _sended_order;
+ std::stringstream _report;
+ bool _test_result = true;
+ bool _order_test_result = true;
+
+ void reset() {
+ for (const auto& en : _builder->entries_) {
+ _sended_order.push_back(en);
+ }
+ _current_size = 0, _entry_counter = 0;
+ _control_set.clear();
+ _builder->clear();
+ }
+
+ size_t expected_size() { return Builder::kHeaderSize() + _current_size; }
+
+ public:
+ std::string getReport() const { return _report.str(); }
+
+ AttributesResponseBuilderTestUser(size_t m_size, Maker maker)
+ : maker(maker), _builder(maker(m_size)), _mtu(m_size) {
+ _report << __func__ << ": mtu \"" << _mtu << "\"\n";
+ }
+
+ void startTest(size_t m_size) {
+ _builder = maker(m_size);
+ _mtu = m_size;
+ reset();
+ _report.str("");
+ _report.clear();
+ _order_control.clear();
+ _sended_order.clear();
+ _report << __func__ << ": mtu \"" << _mtu << "\"\n";
+ _order_test_result = true;
+ _test_result = true;
+ }
+
+ bool testResult() const { return _test_result; }
+
+ bool testOrder() { return _order_test_result; }
+
+ void finishTest() {
+ reset();
+ if (_order_control.size() != _sended_order.size()) {
+ _report << __func__ << ": testOrder FAIL: "
+ << "the count of entries which should send ("
+ << _order_control.size() << ") is not equal to sended entries("
+ << _sended_order.size() << ")) \n input:\n "
+ << to_string(_order_control) << "\n sended:\n"
+ << to_string(_sended_order) << "\n";
+ _order_test_result = false;
+ return;
+ }
+ auto e = _order_control.begin();
+ auto s = _sended_order.begin();
+ for (; e != _order_control.end(); ++e, ++s) {
+ if (*e != *s) {
+ _report << __func__ << "testOrder FAIL: order of entries was changed\n";
+ _order_test_result = false;
+ break;
+ }
+ }
+ _report << __func__ << ": mtu \"" << _mtu << "\"\n";
+ }
+
+ void AddAttributeEntry(AttributeEntry entry) {
+ auto f = _builder->AddAttributeEntry(entry);
+ if (f != 0) {
+ _current_size += f;
+ ++_entry_counter;
+ }
+ if (f == entry.size()) {
+ wholeEntry(f, std::move(entry));
+ } else {
+ fractionEntry(f, std::move(entry));
+ }
+ }
+
+ private:
+ void wholeEntry(size_t f, AttributeEntry&& entry) {
+ _control_set.insert(entry);
+ _order_control.push_back(entry);
+ if (_builder->size() != expected_size()) {
+ _report << __func__ << "FAIL for \"" << to_string(entry)
+ << "\": not allowed to add.\n";
+ _test_result = false;
+ }
+ }
+
+ void fractionEntry(size_t f, AttributeEntry&& entry) {
+ auto l_value = entry.value().size() - (entry.size() - f);
+ if (f != 0) {
+ auto pushed_entry = AttributeEntry(
+ entry.attribute(), std::string(entry.value(), 0, l_value));
+ _control_set.insert(pushed_entry);
+ _order_control.push_back(pushed_entry);
+ }
+
+ if (expected_size() != _builder->size()) {
+ _test_result = false;
+ _report << __func__ << "FAIL for \"" << to_string(entry)
+ << "\": not allowed to add.\n";
+ }
+
+ if (_builder->size() != expected_size() ||
+ _builder->entries_.size() != _entry_counter) {
+ _report << __func__ << "FAIL for \"" << to_string(entry)
+ << "\": unexpected size of packet\n";
+ _test_result = false;
+ }
+ for (auto dat = _builder->entries_.begin(), ex = _control_set.begin();
+ ex != _control_set.end(); ++dat, ++ex) {
+ if (*dat != *ex) {
+ _report << __func__ << "FAIL for \"" << to_string(entry)
+ << "\": unexpected entry order\n";
+ _test_result = false;
+ }
+ }
+ auto tail = (f == 0) ? entry
+ : AttributeEntry(entry.attribute(),
+ std::string(entry.value(), l_value));
+ if (_builder->entries_.size() != 0) {
+ reset();
+ AddAttributeEntry(tail);
+ }
+ if (_builder->entries_.size() == 0) {
+ _report << __func__ << "FAIL: MTU " << _mtu << " too small\n";
+ _test_result = false;
+ _order_control.push_back(entry);
+ reset();
+ }
+ }
+};
+
+template <class AttributesBuilder>
+class FragmentationBuilderHelper {
+ public:
+ using Builder = AttributesBuilder;
+ using Helper = AttributesResponseBuilderTestUser<Builder>;
+ using Maker = typename Helper::Maker;
+
+ FragmentationBuilderHelper(size_t mtu, Maker m) : _helper(mtu, m) {}
+
+ template <class TestCollection>
+ void runTest(const TestCollection& test_data, size_t mtu,
+ bool expect_fragmentation = true, bool expect_ordering = true) {
+ _helper.startTest(mtu);
+
+ for (auto& i : test_data) {
+ _helper.AddAttributeEntry(i);
+ }
+ _helper.finishTest();
+
+ EXPECT_EQ(expect_fragmentation, _helper.testResult())
+ << "Report: " << _helper.getReport();
+ EXPECT_EQ(expect_ordering, _helper.testOrder())
+ << "Report: " << _helper.getReport();
+ }
+
+ private:
+ Helper _helper;
+};
+} // namespace avrcp
+} // namespace bluetooth
diff --git a/system/stack/Android.bp b/system/stack/Android.bp
index 73b8a53..3db56b9 100644
--- a/system/stack/Android.bp
+++ b/system/stack/Android.bp
@@ -207,6 +207,7 @@
"libbt-hci",
],
whole_static_libs: [
+ "libcom.android.sysprop.bluetooth",
"libldacBT_abr",
"libldacBT_enc",
"libaptx_enc",
@@ -235,6 +236,7 @@
srcs: [
"test/stack_a2dp_test.cc",
"test/stack_avrcp_test.cc",
+ "test/gatt/gatt_api_test.cc",
],
shared_libs: [
"android.hardware.bluetooth@1.0",
@@ -481,8 +483,12 @@
// Bluetooth stack connection multiplexing
cc_test {
name: "net_test_gatt_conn_multiplexing",
- defaults: ["fluoride_defaults"],
+ defaults: [
+ "fluoride_defaults",
+ "mts_defaults",
+ ],
host_supported: true,
+ test_suites: ["general-tests"],
local_include_dirs: [
"include",
"btm",
@@ -937,6 +943,7 @@
":BluetoothHalSources_hci_host",
":BluetoothOsSources_host",
":TestCommonMainHandler",
+ ":TestCommonMockFunctions",
":TestMockBta",
":TestMockBtif",
":TestMockDevice",
@@ -978,6 +985,7 @@
"btm/btm_sec.cc",
"metrics/stack_metrics_logging.cc",
"test/btm/stack_btm_test.cc",
+ "test/btm/stack_btm_regression_tests.cc",
"test/btm/peer_packet_types_test.cc",
"test/common/mock_eatt.cc",
],
@@ -997,9 +1005,14 @@
"libprotobuf-cpp-lite",
],
sanitize: {
- hwaddress: true,
- integer_overflow: true,
- scs: true,
+ address: true,
+ all_undefined: true,
+ cfi: true,
+ integer_overflow: true,
+ scs: true,
+ diag: {
+ undefined : true
+ },
},
}
@@ -1289,6 +1302,7 @@
"libbt-common",
"libbt-protos-lite",
"libbtdevice",
+ "libflatbuffers-cpp",
"libgmock",
"liblog",
"libosi",
@@ -1296,7 +1310,6 @@
shared_libs: [
"libbinder_ndk",
"libcrypto",
- "libflatbuffers-cpp",
"libprotobuf-cpp-lite",
],
sanitize: {
@@ -1359,6 +1372,7 @@
static_libs: [
"libbt-common",
"libbt-protos-lite",
+ "libflatbuffers-cpp",
"libgmock",
"liblog",
"libosi",
@@ -1366,7 +1380,6 @@
shared_libs: [
"libbinder_ndk",
"libcrypto",
- "libflatbuffers-cpp",
"libprotobuf-cpp-lite",
],
sanitize: {
diff --git a/system/stack/a2dp/a2dp_sbc.cc b/system/stack/a2dp/a2dp_sbc.cc
index c5d5a15..5a95099 100644
--- a/system/stack/a2dp/a2dp_sbc.cc
+++ b/system/stack/a2dp/a2dp_sbc.cc
@@ -696,6 +696,11 @@
return false;
}
+ // there is an 4-byte timestamp right following p_buf
+ if (p_buf->offset < 4 + A2DP_SBC_MPL_HDR_LEN) {
+ return false;
+ }
+
p_buf->offset -= A2DP_SBC_MPL_HDR_LEN;
uint8_t* p = (uint8_t*)(p_buf + 1) + p_buf->offset;
p_buf->len += A2DP_SBC_MPL_HDR_LEN;
diff --git a/system/stack/a2dp/a2dp_vendor_ldac.cc b/system/stack/a2dp/a2dp_vendor_ldac.cc
index b6aaee5..e8430ff 100644
--- a/system/stack/a2dp/a2dp_vendor_ldac.cc
+++ b/system/stack/a2dp/a2dp_vendor_ldac.cc
@@ -518,6 +518,11 @@
uint16_t frames_per_packet) {
uint8_t* p;
+ // there is a 4 byte timestamp right following p_buf
+ if (p_buf->offset < 4 + A2DP_LDAC_MPL_HDR_LEN) {
+ return false;
+ }
+
p_buf->offset -= A2DP_LDAC_MPL_HDR_LEN;
p = (uint8_t*)(p_buf + 1) + p_buf->offset;
p_buf->len += A2DP_LDAC_MPL_HDR_LEN;
diff --git a/system/stack/acl/btm_acl.cc b/system/stack/acl/btm_acl.cc
index cda4d85..eb74e90 100644
--- a/system/stack/acl/btm_acl.cc
+++ b/system/stack/acl/btm_acl.cc
@@ -50,6 +50,7 @@
#include "main/shim/dumpsys.h"
#include "main/shim/l2c_api.h"
#include "main/shim/shim.h"
+#include "os/parameter_provider.h"
#include "osi/include/allocator.h"
#include "osi/include/log.h"
#include "osi/include/osi.h" // UNUSED_ATTR
@@ -339,6 +340,11 @@
uint8_t sca;
uint8_t status;
+ if (len < 4) {
+ LOG_WARN("Malformatted packet, not containing enough data");
+ return;
+ }
+
STREAM_TO_UINT8(status, data);
if (status != HCI_SUCCESS) {
@@ -630,6 +636,23 @@
return;
}
+ /* Common Criteria mode only: if we are trying to drop encryption on an
+ * encrypted connection, drop the connection */
+ if (bluetooth::os::ParameterProvider::IsCommonCriteriaMode()) {
+ if (p->is_encrypted && !encr_enable) {
+ LOG(ERROR)
+ << __func__
+ << " attempting to decrypt encrypted connection, disconnecting. "
+ "handle: "
+ << loghex(handle);
+
+ acl_disconnect_from_handle(handle, HCI_ERR_HOST_REJECT_SECURITY,
+ "stack::btu::btu_hcif::read_drop_encryption "
+ "Connection Already Encrypted");
+ return;
+ }
+ }
+
p->is_encrypted = encr_enable;
/* Process Role Switch if active */
@@ -1754,7 +1777,7 @@
* Returns void
*
******************************************************************************/
-void btm_read_tx_power_complete(uint8_t* p, bool is_ble) {
+void btm_read_tx_power_complete(uint8_t* p, uint16_t evt_len, bool is_ble) {
tBTM_CMPL_CB* p_cb = btm_cb.devcb.p_tx_power_cmpl_cb;
tBTM_TX_POWER_RESULT result;
@@ -1763,6 +1786,10 @@
/* If there was a registered callback, call it */
if (p_cb) {
+ if (evt_len < 1) {
+ goto err_out;
+ }
+
STREAM_TO_UINT8(result.hci_status, p);
if (result.hci_status == HCI_SUCCESS) {
@@ -1770,6 +1797,11 @@
if (!is_ble) {
uint16_t handle;
+
+ if (evt_len < 4) {
+ goto err_out;
+ }
+
STREAM_TO_UINT16(handle, p);
STREAM_TO_UINT8(result.tx_power, p);
@@ -1778,6 +1810,10 @@
result.rem_bda = p_acl_cb->remote_addr;
}
} else {
+ if (evt_len < 2) {
+ goto err_out;
+ }
+
STREAM_TO_UINT8(result.tx_power, p);
result.rem_bda = btm_cb.devcb.read_tx_pwr_addr;
}
@@ -1791,6 +1827,11 @@
(*p_cb)(&result);
}
+
+ return;
+
+ err_out:
+ LOG_ERROR("Bogus event packet, too short");
}
/*******************************************************************************
@@ -1820,7 +1861,7 @@
* Returns void
*
******************************************************************************/
-void btm_read_rssi_complete(uint8_t* p) {
+void btm_read_rssi_complete(uint8_t* p, uint16_t evt_len) {
tBTM_CMPL_CB* p_cb = btm_cb.devcb.p_rssi_cmpl_cb;
tBTM_RSSI_RESULT result;
@@ -1829,11 +1870,19 @@
/* If there was a registered callback, call it */
if (p_cb) {
+ if (evt_len < 1) {
+ goto err_out;
+ }
+
STREAM_TO_UINT8(result.hci_status, p);
result.status = BTM_ERR_PROCESSING;
if (result.hci_status == HCI_SUCCESS) {
uint16_t handle;
+
+ if (evt_len < 4) {
+ goto err_out;
+ }
STREAM_TO_UINT16(handle, p);
STREAM_TO_UINT8(result.rssi, p);
@@ -1849,6 +1898,11 @@
}
(*p_cb)(&result);
}
+
+ return;
+
+err_out:
+ LOG_ERROR("Bogus event packet, too short");
}
/*******************************************************************************
@@ -1983,7 +2037,7 @@
* Returns void
*
******************************************************************************/
-void btm_read_link_quality_complete(uint8_t* p) {
+void btm_read_link_quality_complete(uint8_t* p, uint16_t evt_len) {
tBTM_CMPL_CB* p_cb = btm_cb.devcb.p_link_qual_cmpl_cb;
tBTM_LINK_QUALITY_RESULT result;
@@ -1992,12 +2046,20 @@
/* If there was a registered callback, call it */
if (p_cb) {
+ if (evt_len < 1) {
+ goto err_out;
+ }
+
STREAM_TO_UINT8(result.hci_status, p);
if (result.hci_status == HCI_SUCCESS) {
uint16_t handle;
result.status = BTM_SUCCESS;
+ if (evt_len < 4) {
+ goto err_out;
+ }
+
STREAM_TO_UINT16(handle, p);
STREAM_TO_UINT8(result.link_quality, p);
@@ -2016,6 +2078,11 @@
(*p_cb)(&result);
}
+
+ return;
+
+err_out:
+ LOG_ERROR("Bogus Link Quality event packet, size: %d", evt_len);
}
/*******************************************************************************
diff --git a/system/stack/avdt/avdt_scb.cc b/system/stack/avdt/avdt_scb.cc
index bcb8a1a..f67b396 100644
--- a/system/stack/avdt/avdt_scb.cc
+++ b/system/stack/avdt/avdt_scb.cc
@@ -228,7 +228,7 @@
/* TC_DATA_EVT */
{AVDT_SCB_DROP_PKT, AVDT_SCB_IGNORE, AVDT_SCB_IDLE_ST},
/* CC_CLOSE_EVT */
- {AVDT_SCB_CLR_VARS, AVDT_SCB_IGNORE, AVDT_SCB_IDLE_ST}};
+ {AVDT_SCB_HDL_TC_CLOSE, AVDT_SCB_CLR_VARS, AVDT_SCB_IDLE_ST}};
/* state table for configured state */
const uint8_t avdt_scb_st_conf[][AVDT_SCB_NUM_COLS] = {
diff --git a/system/stack/avrc/avrc_api.cc b/system/stack/avrc/avrc_api.cc
index 9cbb2fe..dab1d24 100644
--- a/system/stack/avrc/avrc_api.cc
+++ b/system/stack/avrc/avrc_api.cc
@@ -23,6 +23,9 @@
******************************************************************************/
#include "avrc_api.h"
+#ifdef OS_ANDROID
+#include <avrcp.sysprop.h>
+#endif
#include <base/logging.h>
#include <string.h>
@@ -77,6 +80,25 @@
/******************************************************************************
*
+ * Function avrcp_absolute_volume_is_enabled
+ *
+ * Description Check if config support advance control (absolute volume)
+ *
+ * Returns return true if absolute_volume is enabled
+ *
+ *****************************************************************************/
+bool avrcp_absolute_volume_is_enabled() {
+#ifdef OS_ANDROID
+ static const bool absolute_volume =
+ android::sysprop::bluetooth::Avrcp::absolute_volume().value_or(true);
+ return absolute_volume;
+#else
+ return true;
+#endif
+}
+
+/******************************************************************************
+ *
* Function avrc_ctrl_cback
*
* Description This is the callback function used by AVCTP to report
diff --git a/system/stack/avrc/avrc_bld_ct.cc b/system/stack/avrc/avrc_bld_ct.cc
index deadd29..5eb5d3b 100644
--- a/system/stack/avrc/avrc_bld_ct.cc
+++ b/system/stack/avrc/avrc_bld_ct.cc
@@ -58,7 +58,6 @@
* the following commands are introduced in AVRCP 1.4
****************************************************************************/
-#if (AVRC_ADV_CTRL_INCLUDED == TRUE)
/*******************************************************************************
*
* Function avrc_bld_set_abs_volume_cmd
@@ -110,7 +109,6 @@
p_pkt->len = (p_data - p_start);
return AVRC_STS_NO_ERROR;
}
-#endif
/*******************************************************************************
*
@@ -607,16 +605,18 @@
case AVRC_PDU_ABORT_CONTINUATION_RSP: /* 0x41 */
status = avrc_bld_next_cmd(&p_cmd->abort, p_pkt);
break;
-#if (AVRC_ADV_CTRL_INCLUDED == TRUE)
case AVRC_PDU_SET_ABSOLUTE_VOLUME: /* 0x50 */
+ if (!avrcp_absolute_volume_is_enabled()) {
+ break;
+ }
status = avrc_bld_set_abs_volume_cmd(&p_cmd->volume, p_pkt);
break;
-#endif
case AVRC_PDU_REGISTER_NOTIFICATION: /* 0x31 */
-#if (AVRC_ADV_CTRL_INCLUDED == TRUE)
+ if (!avrcp_absolute_volume_is_enabled()) {
+ break;
+ }
status = avrc_bld_register_notifn(p_pkt, p_cmd->reg_notif.event_id,
p_cmd->reg_notif.param);
-#endif
break;
case AVRC_PDU_GET_CAPABILITIES:
status =
diff --git a/system/stack/avrc/avrc_pars_ct.cc b/system/stack/avrc/avrc_pars_ct.cc
index cee8ea4..9dc8a77 100644
--- a/system/stack/avrc/avrc_pars_ct.cc
+++ b/system/stack/avrc/avrc_pars_ct.cc
@@ -48,9 +48,7 @@
tAVRC_STS status = AVRC_STS_NO_ERROR;
uint8_t* p;
uint16_t len;
-#if (AVRC_ADV_CTRL_INCLUDED == TRUE)
uint8_t eventid = 0;
-#endif
/* Check the vendor data */
if (p_msg->vendor_len == 0) return AVRC_STS_NO_ERROR;
@@ -88,18 +86,21 @@
/* case AVRC_PDU_REQUEST_CONTINUATION_RSP: 0x40 */
/* case AVRC_PDU_ABORT_CONTINUATION_RSP: 0x41 */
-#if (AVRC_ADV_CTRL_INCLUDED == TRUE)
case AVRC_PDU_SET_ABSOLUTE_VOLUME: /* 0x50 */
+ if (!avrcp_absolute_volume_is_enabled()) {
+ break;
+ }
if (len != 1)
status = AVRC_STS_INTERNAL_ERR;
else {
BE_STREAM_TO_UINT8(p_result->volume.volume, p);
}
break;
-#endif /* (AVRC_ADV_CTRL_INCLUDED == TRUE) */
case AVRC_PDU_REGISTER_NOTIFICATION: /* 0x31 */
-#if (AVRC_ADV_CTRL_INCLUDED == TRUE)
+ if (!avrcp_absolute_volume_is_enabled()) {
+ break;
+ }
if (len < 1) {
AVRC_TRACE_WARNING(
"%s: invalid parameter length %d: must be at least 1", __func__,
@@ -124,7 +125,6 @@
}
AVRC_TRACE_DEBUG("%s PDU reg notif response:event %x, volume %x",
__func__, eventid, p_result->reg_notif.param.volume);
-#endif /* (AVRC_ADV_CTRL_INCLUDED == TRUE) */
break;
default:
status = AVRC_STS_BAD_CMD;
diff --git a/system/stack/btm/ble_scanner_hci_interface.cc b/system/stack/btm/ble_scanner_hci_interface.cc
index 5d3686b..2e521f2 100644
--- a/system/stack/btm/ble_scanner_hci_interface.cc
+++ b/system/stack/btm/ble_scanner_hci_interface.cc
@@ -418,6 +418,11 @@
uint8_t* data) {
uint16_t sync_handle;
+ if (data_len < 2) {
+ LOG(ERROR) << "Bogus event packet, too short";
+ return;
+ }
+
STREAM_TO_UINT16(sync_handle, data);
if (BleScannerHciInterface::Get()) {
diff --git a/system/stack/btm/btm_ble.cc b/system/stack/btm/btm_ble.cc
index 0e59a1f..1227f99 100644
--- a/system/stack/btm/btm_ble.cc
+++ b/system/stack/btm/btm_ble.cc
@@ -62,6 +62,22 @@
#define PROPERTY_BLE_PRIVACY_ENABLED "bluetooth.core.gap.le.privacy.enabled"
#endif
+// Pairing parameters defined in Vol 3, Part H, Chapter 3.5.1 - 3.5.2
+// All present in the exact decimal values, not hex
+// Ex: bluetooth.core.smp.le.ctkd.initiator_key_distribution 15(0x0f)
+static const char kPropertyCtkdAuthRequest[] =
+ "bluetooth.core.smp.le.ctkd.auth_request";
+static const char kPropertyCtkdIoCapabilities[] =
+ "bluetooth.core.smp.le.ctkd.io_capabilities";
+// Vol 3, Part H, Chapter 3.6.1, Figure 3.11
+// |EncKey(1)|IdKey(1)|SignKey(1)|LinkKey(1)|Reserved(4)|
+static const char kPropertyCtkdInitiatorKeyDistribution[] =
+ "bluetooth.core.smp.le.ctkd.initiator_key_distribution";
+static const char kPropertyCtkdResponderKeyDistribution[] =
+ "bluetooth.core.smp.le.ctkd.responder_key_distribution";
+static const char kPropertyCtkdMaxKeySize[] =
+ "bluetooth.core.smp.le.ctkd.max_key_size";
+
/******************************************************************************/
/* External Function to be called by other modules */
/******************************************************************************/
@@ -488,9 +504,9 @@
}
} else /* there is a security device record exisitng */
{
- /* new inquiry result, overwrite device type in security device record */
+ /* new inquiry result, merge device type in security device record */
if (p_inq_info) {
- p_dev_rec->device_type = p_inq_info->results.device_type;
+ p_dev_rec->device_type |= p_inq_info->results.device_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
@@ -1019,7 +1035,8 @@
* Returns void
*
******************************************************************************/
-void btm_ble_rand_enc_complete(uint8_t* p, uint16_t op_code,
+void btm_ble_rand_enc_complete(uint8_t* p, uint16_t evt_len,
+ uint16_t op_code,
tBTM_RAND_ENC_CB* p_enc_cplt_cback) {
tBTM_RAND_ENC params;
uint8_t* p_dest = params.param_buf;
@@ -1030,6 +1047,11 @@
/* If there was a callback address for vcs complete, call it */
if (p_enc_cplt_cback && p) {
+
+ if (evt_len < 1) {
+ goto err_out;
+ }
+
/* Pass paramters to the callback function */
STREAM_TO_UINT8(params.status, p); /* command status */
@@ -1041,12 +1063,21 @@
else
params.param_len = OCTET16_LEN;
+ if (evt_len < 1 + params.param_len) {
+ goto err_out;
+ }
+
/* Fetch return info from HCI event message */
memcpy(p_dest, p, params.param_len);
}
if (p_enc_cplt_cback) /* Call the Encryption complete callback function */
(*p_enc_cplt_cback)(¶ms);
}
+
+ return;
+
+err_out:
+ BTM_TRACE_ERROR("%s malformatted event packet, too short", __func__);
}
/*******************************************************************************
@@ -1592,13 +1623,30 @@
BTM_TRACE_ERROR("key size = %d", p_rec->ble.keys.key_size);
if (use_stk) {
btsnd_hcic_ble_ltk_req_reply(btm_cb.enc_handle, stk);
- } else /* calculate LTK using peer device */
- {
- if (p_rec->ble.key_type & BTM_LE_KEY_LENC)
- btsnd_hcic_ble_ltk_req_reply(btm_cb.enc_handle, p_rec->ble.keys.lltk);
- else
- btsnd_hcic_ble_ltk_req_neg_reply(btm_cb.enc_handle);
+ return;
}
+ /* calculate LTK using peer device */
+ if (p_rec->ble.key_type & BTM_LE_KEY_LENC) {
+ btsnd_hcic_ble_ltk_req_reply(btm_cb.enc_handle, p_rec->ble.keys.lltk);
+ return;
+ }
+
+ p_rec = btm_find_dev_with_lenc(bda);
+ if (!p_rec) {
+ btsnd_hcic_ble_ltk_req_neg_reply(btm_cb.enc_handle);
+ return;
+ }
+
+ LOG_INFO("Found second sec_dev_rec for device that have LTK");
+ /* This can happen when remote established LE connection using RPA to this
+ * device, but then pair with us using Classing transport while still keeping
+ * LE connection. If remote attempts to encrypt the LE connection, we might
+ * end up here. We will eventually consolidate both entries, this is to avoid
+ * race conditions. */
+
+ LOG_ASSERT(p_rec->ble.key_type & BTM_LE_KEY_LENC);
+ p_cb->key_size = p_rec->ble.keys.key_size;
+ btsnd_hcic_ble_ltk_req_reply(btm_cb.enc_handle, p_rec->ble.keys.lltk);
}
/*******************************************************************************
@@ -1699,11 +1747,18 @@
tBTM_LE_IO_REQ* p_data) {
uint8_t callback_rc = BTM_SUCCESS;
BTM_TRACE_DEBUG("%s", __func__);
- if (btm_cb.api.p_le_callback) {
- /* the callback function implementation may change the IO capability... */
- callback_rc = (*btm_cb.api.p_le_callback)(
- BTM_LE_IO_REQ_EVT, p_dev_rec->bd_addr, (tBTM_LE_EVT_DATA*)p_data);
- }
+ p_data->io_cap =
+ osi_property_get_int32(kPropertyCtkdIoCapabilities, BTM_IO_CAP_UNKNOWN);
+ p_data->auth_req = osi_property_get_int32(kPropertyCtkdAuthRequest,
+ BTM_LE_AUTH_REQ_SC_MITM_BOND);
+ p_data->init_keys = osi_property_get_int32(
+ kPropertyCtkdInitiatorKeyDistribution, SMP_BR_SEC_DEFAULT_KEY);
+ p_data->resp_keys = osi_property_get_int32(
+ kPropertyCtkdResponderKeyDistribution, SMP_BR_SEC_DEFAULT_KEY);
+ p_data->max_key_size =
+ osi_property_get_int32(kPropertyCtkdMaxKeySize, BTM_BLE_MAX_KEY_SIZE);
+ // No OOB data for BR/EDR
+ p_data->oob_data = false;
return callback_rc;
}
diff --git a/system/stack/btm/btm_ble_batchscan.cc b/system/stack/btm/btm_ble_batchscan.cc
index 2278880..bdce367 100644
--- a/system/stack/btm/btm_ble_batchscan.cc
+++ b/system/stack/btm/btm_ble_batchscan.cc
@@ -60,12 +60,15 @@
/* VSE callback for batch scan, filter, and tracking events */
void btm_ble_batchscan_filter_track_adv_vse_cback(uint8_t len,
const uint8_t* p) {
- tBTM_BLE_TRACK_ADV_DATA adv_data;
-
+ tBTM_BLE_TRACK_ADV_DATA adv_data{};
uint8_t sub_event = 0;
tBTM_BLE_VSC_CB cmn_ble_vsc_cb;
- if (len == 0) return;
+
+ if (len < 1)
+ goto err_out;
+
STREAM_TO_UINT8(sub_event, p);
+ len -= 1;
BTM_TRACE_EVENT(
"btm_ble_batchscan_filter_track_adv_vse_cback called with event:%x",
@@ -78,30 +81,45 @@
if (HCI_VSE_SUBCODE_BLE_TRACKING_SUB_EVT == sub_event &&
NULL != ble_advtrack_cb.p_track_cback) {
- if (len < 10) return;
- memset(&adv_data, 0, sizeof(tBTM_BLE_TRACK_ADV_DATA));
BTM_BleGetVendorCapabilities(&cmn_ble_vsc_cb);
adv_data.client_if = (uint8_t)ble_advtrack_cb.ref_value;
if (cmn_ble_vsc_cb.version_supported > BTM_VSC_CHIP_CAPABILITY_L_VERSION) {
+ if (len < 10) {
+ goto err_out;
+ }
+
STREAM_TO_UINT8(adv_data.filt_index, p);
STREAM_TO_UINT8(adv_data.advertiser_state, p);
STREAM_TO_UINT8(adv_data.advertiser_info_present, p);
STREAM_TO_BDADDR(adv_data.bd_addr, p);
STREAM_TO_UINT8(adv_data.addr_type, p);
+ len -= 10;
+
/* Extract the adv info details */
if (ADV_INFO_PRESENT == adv_data.advertiser_info_present) {
- if (len < 15) return;
+
+ if (len < 5) {
+ goto err_out;
+ }
+
STREAM_TO_UINT8(adv_data.tx_power, p);
STREAM_TO_UINT8(adv_data.rssi_value, p);
STREAM_TO_UINT16(adv_data.time_stamp, p);
-
STREAM_TO_UINT8(adv_data.adv_pkt_len, p);
+
+ len -= 5;
+
if (adv_data.adv_pkt_len > 0) {
adv_data.p_adv_pkt_data =
static_cast<uint8_t*>(osi_malloc(adv_data.adv_pkt_len));
+ if (adv_data.p_adv_pkt_data == nullptr || \
+ len < adv_data.adv_pkt_len) {
+ goto err_out;
+ }
memcpy(adv_data.p_adv_pkt_data, p, adv_data.adv_pkt_len);
+ len -= adv_data.adv_pkt_len;
p += adv_data.adv_pkt_len;
}
@@ -109,11 +127,19 @@
if (adv_data.scan_rsp_len > 0) {
adv_data.p_scan_rsp_data =
static_cast<uint8_t*>(osi_malloc(adv_data.scan_rsp_len));
+
+ if (adv_data.p_scan_rsp_data == nullptr || len < adv_data.scan_rsp_len) {
+ goto err_out;
+ }
memcpy(adv_data.p_scan_rsp_data, p, adv_data.scan_rsp_len);
}
}
} else {
/* Based on L-release version */
+ if (len < 9) {
+ goto err_out;
+ }
+
STREAM_TO_UINT8(adv_data.filt_index, p);
STREAM_TO_UINT8(adv_data.addr_type, p);
STREAM_TO_BDADDR(adv_data.bd_addr, p);
@@ -129,8 +155,15 @@
to_ble_addr_type(adv_data.addr_type));
ble_advtrack_cb.p_track_cback(&adv_data);
- return;
}
+
+ return;
+
+err_out:
+ BTM_TRACE_ERROR("malformatted packet detected");
+
+ osi_free_and_reset((void **) &adv_data.p_adv_pkt_data);
+ osi_free_and_reset((void **) &adv_data.p_scan_rsp_data);
}
void feat_enable_cb(uint8_t* p, uint16_t len) {
diff --git a/system/stack/btm/btm_ble_gap.cc b/system/stack/btm/btm_ble_gap.cc
index f8a8940..ff28c2c 100644
--- a/system/stack/btm/btm_ble_gap.cc
+++ b/system/stack/btm/btm_ble_gap.cc
@@ -1436,7 +1436,7 @@
******************************************************************************/
void btm_ble_periodic_adv_sync_tx_rcvd(uint8_t* p, uint16_t param_len) {
LOG_DEBUG("[PAST]: PAST received, param_len=%u", param_len);
- if (param_len == 0) {
+ if (param_len < 19) {
LOG_ERROR("%s", "Insufficient data");
return;
}
@@ -2431,15 +2431,18 @@
}
const uint8_t* p_service_data = data.data();
- uint16_t remaining_data_len = data.size();
uint8_t service_data_len = 0;
while ((p_service_data = AdvertiseDataParser::GetFieldByType(
p_service_data + service_data_len,
- (remaining_data_len -= service_data_len),
+ data.size() - (p_service_data - data.data()) - service_data_len,
BTM_BLE_AD_TYPE_SERVICE_DATA_TYPE, &service_data_len))) {
uint16_t uuid;
- STREAM_TO_UINT16(uuid, p_service_data);
+ const uint8_t* p_uuid = p_service_data;
+ if (service_data_len < 2) {
+ continue;
+ }
+ STREAM_TO_UINT16(uuid, p_uuid);
if (uuid == 0x184E /* Audio Stream Control service */ ||
uuid == 0x184F /* Broadcast Audio Scan service */ ||
@@ -3197,9 +3200,14 @@
* Returns void
*
******************************************************************************/
-void btm_ble_read_remote_features_complete(uint8_t* p) {
+void btm_ble_read_remote_features_complete(uint8_t* p, uint8_t length) {
uint16_t handle;
uint8_t status;
+
+ if (length < 3) {
+ goto err_out;
+ }
+
STREAM_TO_UINT8(status, p);
STREAM_TO_UINT16(handle, p);
handle = handle & 0x0FFF; // only 12 bits meaningful
@@ -3214,6 +3222,12 @@
}
if (status == HCI_SUCCESS) {
+ // BD_FEATURES_LEN additional bytes are read
+ // in acl_set_peer_le_features_from_handle
+ if (length < 3 + BD_FEATURES_LEN) {
+ goto err_out;
+ }
+
if (!acl_set_peer_le_features_from_handle(handle, p)) {
LOG_ERROR(
"Unable to find existing connection after read remote features");
@@ -3222,6 +3236,10 @@
}
btsnd_hcic_rmt_ver_req(handle);
+ return;
+
+err_out:
+ LOG_ERROR("bogus event packet, too short");
}
/*******************************************************************************
@@ -3233,11 +3251,11 @@
* Returns void
*
******************************************************************************/
-void btm_ble_write_adv_enable_complete(uint8_t* p) {
+void btm_ble_write_adv_enable_complete(uint8_t* p, uint16_t evt_len) {
tBTM_BLE_INQ_CB* p_cb = &btm_cb.ble_ctr_cb.inq_var;
/* if write adv enable/disbale not succeed */
- if (*p != HCI_SUCCESS) {
+ if (evt_len < 1 || *p != HCI_SUCCESS) {
/* toggle back the adv mode */
p_cb->adv_mode = !p_cb->adv_mode;
}
diff --git a/system/stack/btm/btm_ble_privacy.cc b/system/stack/btm/btm_ble_privacy.cc
index b2f26fa..6dafc4a 100644
--- a/system/stack/btm/btm_ble_privacy.cc
+++ b/system/stack/btm/btm_ble_privacy.cc
@@ -224,6 +224,12 @@
******************************************************************************/
void btm_ble_clear_resolving_list_complete(uint8_t* p, uint16_t evt_len) {
uint8_t status = 0;
+
+ if (evt_len < 1) {
+ BTM_TRACE_ERROR("malformatted event packet: containing zero bytes");
+ return;
+ }
+
STREAM_TO_UINT8(status, p);
BTM_TRACE_DEBUG("%s status=%d", __func__, status);
@@ -268,6 +274,12 @@
******************************************************************************/
void btm_ble_add_resolving_list_entry_complete(uint8_t* p, uint16_t evt_len) {
uint8_t status;
+
+ if (evt_len < 1) {
+ BTM_TRACE_ERROR("malformatted event packet: containing zero bytes");
+ return;
+ }
+
STREAM_TO_UINT8(status, p);
BTM_TRACE_DEBUG("%s status = %d", __func__, status);
diff --git a/system/stack/btm/btm_dev.cc b/system/stack/btm/btm_dev.cc
index 4067e3d..10c19d4 100644
--- a/system/stack/btm/btm_dev.cc
+++ b/system/stack/btm/btm_dev.cc
@@ -30,6 +30,7 @@
#include <string.h>
#include "btm_api.h"
+#include "btm_ble_int.h"
#include "device/include/controller.h"
#include "l2c_api.h"
#include "main/shim/btm_api.h"
@@ -372,6 +373,32 @@
return NULL;
}
+static bool has_lenc_and_address_is_equal(void* data, void* context) {
+ tBTM_SEC_DEV_REC* p_dev_rec = static_cast<tBTM_SEC_DEV_REC*>(data);
+ if (!(p_dev_rec->ble.key_type & BTM_LE_KEY_LENC)) return true;
+
+ return is_address_equal(data, context);
+}
+
+/*******************************************************************************
+ *
+ * Function btm_find_dev_with_lenc
+ *
+ * Description Look for the record in the device database with LTK and
+ * specified BD address
+ *
+ * Returns Pointer to the record or NULL
+ *
+ ******************************************************************************/
+tBTM_SEC_DEV_REC* btm_find_dev_with_lenc(const RawAddress& bd_addr) {
+ if (btm_cb.sec_dev_rec == nullptr) return nullptr;
+
+ list_node_t* n = list_foreach(btm_cb.sec_dev_rec, has_lenc_and_address_is_equal,
+ (void*)&bd_addr);
+ if (n) return static_cast<tBTM_SEC_DEV_REC*>(list_node(n));
+
+ return NULL;
+}
/*******************************************************************************
*
* Function btm_consolidate_dev
@@ -429,6 +456,63 @@
}
}
+/* combine security records of established LE connections after Classic pairing
+ * succeeded. */
+void btm_dev_consolidate_existing_connections(const RawAddress& bd_addr) {
+ tBTM_SEC_DEV_REC* p_target_rec = btm_find_dev(bd_addr);
+ if (!p_target_rec) {
+ LOG_ERROR("No security record for just bonded device!?!?");
+ return;
+ }
+
+ if (p_target_rec->ble_hci_handle != HCI_INVALID_HANDLE) {
+ LOG_INFO("Not consolidating - already have LE connection");
+ return;
+ }
+
+ LOG_INFO("%s", PRIVATE_ADDRESS(bd_addr));
+
+ list_node_t* end = list_end(btm_cb.sec_dev_rec);
+ list_node_t* node = list_begin(btm_cb.sec_dev_rec);
+ while (node != end) {
+ tBTM_SEC_DEV_REC* p_dev_rec =
+ static_cast<tBTM_SEC_DEV_REC*>(list_node(node));
+
+ // we do list_remove in some cases, must grab next before removing
+ node = list_next(node);
+
+ if (p_target_rec == p_dev_rec) continue;
+
+ /* an RPA device entry is a duplicate of the target record */
+ if (btm_ble_addr_resolvable(p_dev_rec->bd_addr, p_target_rec)) {
+ if (p_dev_rec->ble_hci_handle == HCI_INVALID_HANDLE) {
+ LOG_INFO("already disconnected - erasing entry %s",
+ PRIVATE_ADDRESS(p_dev_rec->bd_addr));
+ wipe_secrets_and_remove(p_dev_rec);
+ continue;
+ }
+
+ LOG_INFO(
+ "Found existing LE connection to just bonded device on %s handle 0x%04x",
+ PRIVATE_ADDRESS(p_dev_rec->bd_addr), p_dev_rec->ble_hci_handle);
+
+ RawAddress ble_conn_addr = p_dev_rec->bd_addr;
+ p_target_rec->ble_hci_handle = p_dev_rec->ble_hci_handle;
+
+ /* remove the old LE record */
+ wipe_secrets_and_remove(p_dev_rec);
+
+ /* To avoid race conditions between central/peripheral starting encryption
+ * at same time, initiate it just from central. */
+ if (L2CA_GetBleConnRole(ble_conn_addr) == HCI_ROLE_CENTRAL) {
+ LOG_INFO("Will encrypt existing connection");
+ BTM_SetEncryption(ble_conn_addr, BT_TRANSPORT_LE, nullptr, nullptr,
+ BTM_BLE_SEC_ENCRYPT);
+ }
+ }
+ }
+}
+
/*******************************************************************************
*
* Function btm_find_or_alloc_dev
@@ -566,3 +650,25 @@
p_dev_rec->bond_type = bond_type;
return true;
}
+
+/*******************************************************************************
+ *
+ * Function btm_get_sec_dev_rec
+ *
+ * Description Get security device records satisfying given filter
+ *
+ * Returns A vector containing pointers of security device records
+ *
+ ******************************************************************************/
+std::vector<tBTM_SEC_DEV_REC*> btm_get_sec_dev_rec() {
+ std::vector<tBTM_SEC_DEV_REC*> result{};
+
+ list_node_t* end = list_end(btm_cb.sec_dev_rec);
+ for (list_node_t* node = list_begin(btm_cb.sec_dev_rec); node != end;
+ node = list_next(node)) {
+ tBTM_SEC_DEV_REC* p_dev_rec =
+ static_cast<tBTM_SEC_DEV_REC*>(list_node(node));
+ result.push_back(p_dev_rec);
+ }
+ return result;
+}
diff --git a/system/stack/btm/btm_dev.h b/system/stack/btm/btm_dev.h
index 9da2e89..84d73e7 100644
--- a/system/stack/btm/btm_dev.h
+++ b/system/stack/btm/btm_dev.h
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+#include <functional>
+
#include "osi/include/log.h"
#include "stack/btm/btm_ble_int.h"
#include "stack/btm/security_device_record.h"
@@ -111,8 +113,20 @@
/*******************************************************************************
*
+ * Function btm_find_dev_with_lenc
+ *
+ * Description Look for the record in the device database with LTK and
+ * specified BD address
+ *
+ * Returns Pointer to the record or NULL
+ *
+ ******************************************************************************/
+tBTM_SEC_DEV_REC* btm_find_dev_with_lenc(const RawAddress& bd_addr);
+
+/*******************************************************************************
+ *
* Function btm_consolidate_dev
-5**
+ *
* Description combine security records if identified as same peer
*
* Returns none
@@ -122,6 +136,20 @@
/*******************************************************************************
*
+ * Function btm_consolidate_dev
+ *
+ * Description When pairing is finished (i.e. on BR/EDR), this function
+ * checks if there are existing LE connections to same device
+ * that can now be encrypted and used for profiles requiring
+ * encryption.
+ *
+ * Returns none
+ *
+ ******************************************************************************/
+void btm_dev_consolidate_existing_connections(const RawAddress& bd_addr);
+
+/*******************************************************************************
+ *
* Function btm_find_or_alloc_dev
*
* Description Look for the record in the device database for the record
@@ -171,3 +199,14 @@
******************************************************************************/
bool btm_set_bond_type_dev(const RawAddress& bd_addr,
tBTM_SEC_DEV_REC::tBTM_BOND_TYPE bond_type);
+
+/*******************************************************************************
+ *
+ * Function btm_get_sec_dev_rec
+ *
+ * Description Get security device records satisfying given filter
+ *
+ * Returns A vector containing pointers of security device records
+ *
+ ******************************************************************************/
+std::vector<tBTM_SEC_DEV_REC*> btm_get_sec_dev_rec();
diff --git a/system/stack/btm/btm_devctl.cc b/system/stack/btm/btm_devctl.cc
index b9fb846..30f2dda 100644
--- a/system/stack/btm/btm_devctl.cc
+++ b/system/stack/btm/btm_devctl.cc
@@ -559,18 +559,20 @@
const uint8_t* bqr_ptr = p;
uint8_t event_code;
uint8_t len;
- STREAM_TO_UINT8(event_code, bqr_ptr);
- STREAM_TO_UINT8(len, bqr_ptr);
- // Check if there's at least a subevent code
- if (len > 1 && evt_len > 1 && event_code == HCI_VENDOR_SPECIFIC_EVT) {
- uint8_t sub_event_code;
- STREAM_TO_UINT8(sub_event_code, bqr_ptr);
- if (sub_event_code == HCI_VSE_SUBCODE_BQR_SUB_EVT) {
- // Excluding the HCI Event packet header and 1 octet sub-event code
- int16_t bqr_parameter_length = evt_len - HCIE_PREAMBLE_SIZE - 1;
- const uint8_t* p_bqr_event = bqr_ptr;
- // The stream currently points to the BQR sub-event parameters
- switch (sub_event_code) {
+
+ if (evt_len >= 2) {
+ STREAM_TO_UINT8(event_code, bqr_ptr);
+ STREAM_TO_UINT8(len, bqr_ptr);
+ // Check if there's at least a subevent code
+ if (len > 1 && evt_len >= 2 + 1 && event_code == HCI_VENDOR_SPECIFIC_EVT) {
+ uint8_t sub_event_code;
+ STREAM_TO_UINT8(sub_event_code, bqr_ptr);
+ if (sub_event_code == HCI_VSE_SUBCODE_BQR_SUB_EVT) {
+ // Excluding the HCI Event packet header and 1 octet sub-event code
+ int16_t bqr_parameter_length = evt_len - HCIE_PREAMBLE_SIZE - 1;
+ const uint8_t* p_bqr_event = bqr_ptr;
+ // The stream currently points to the BQR sub-event parameters
+ switch (sub_event_code) {
case bluetooth::bqr::QUALITY_REPORT_ID_LMP_LL_MESSAGE_TRACE:
if (bqr_parameter_length >= bluetooth::bqr::kLogDumpParamTotalLen) {
bluetooth::bqr::DumpLmpLlMessage(bqr_parameter_length, p_bqr_event);
@@ -591,6 +593,7 @@
default:
LOG_INFO("Unhandled BQR subevent 0x%02hxx", sub_event_code);
+ }
}
}
}
@@ -721,7 +724,7 @@
* Returns void
*
******************************************************************************/
-void btm_delete_stored_link_key_complete(uint8_t* p) {
+void btm_delete_stored_link_key_complete(uint8_t* p, uint16_t evt_len) {
tBTM_CMPL_CB* p_cb = btm_cb.devcb.p_stored_link_key_cmpl_cb;
tBTM_DELETE_STORED_LINK_KEY_COMPLETE result;
@@ -732,6 +735,11 @@
/* Set the call back event to indicate command complete */
result.event = BTM_CB_EVT_DELETE_STORED_LINK_KEYS;
+ if (evt_len < 3) {
+ LOG(ERROR) << __func__ << "Malformatted event packet, too short";
+ return;
+ }
+
/* Extract the result fields from the HCI event */
STREAM_TO_UINT8(result.status, p);
STREAM_TO_UINT16(result.num_keys, p);
diff --git a/system/stack/btm/btm_iso_impl.h b/system/stack/btm/btm_iso_impl.h
index 358296e..9559e1d 100644
--- a/system/stack/btm/btm_iso_impl.h
+++ b/system/stack/btm/btm_iso_impl.h
@@ -25,6 +25,7 @@
#include "base/callback.h"
#include "base/logging.h"
#include "bind_helpers.h"
+#include "btm_dev.h"
#include "btm_iso_api.h"
#include "common/time_util.h"
#include "device/include/controller.h"
@@ -33,6 +34,7 @@
#include "osi/include/allocator.h"
#include "osi/include/log.h"
#include "stack/include/bt_hdr.h"
+#include "stack/include/btm_log_history.h"
#include "stack/include/hci_error_code.h"
#include "stack/include/hcidefs.h"
@@ -49,6 +51,8 @@
static constexpr uint8_t kStateFlagHasDataPathSet = 0x04;
static constexpr uint8_t kStateFlagIsBroadcast = 0x10;
+constexpr char kBtmLogTag[] = "ISO";
+
struct iso_sync_info {
uint32_t first_sync_ts;
uint16_t seq_nb;
@@ -118,6 +122,12 @@
uint8_t evt_code = IsCigKnown(cig_id) ? kIsoEventCigOnReconfigureCmpl
: kIsoEventCigOnCreateCmpl;
+ BTM_LogHistory(
+ kBtmLogTag, RawAddress::kEmpty, "CIG Create complete",
+ base::StringPrintf(
+ "cig_id:0x%02x, status: %s", evt.cig_id,
+ hci_status_code_text((tHCI_STATUS)(evt.status)).c_str()));
+
if (evt.status == HCI_SUCCESS) {
LOG_ASSERT(len >= (3) + (cis_cnt * sizeof(uint16_t)))
<< "Invalid CIS count: " << +cis_cnt;
@@ -164,6 +174,11 @@
cig_params.cis_cfgs.size(), cig_params.cis_cfgs.data(),
base::BindOnce(&iso_impl::on_set_cig_params, base::Unretained(this),
cig_id, cig_params.sdu_itv_mtos));
+
+ BTM_LogHistory(
+ kBtmLogTag, RawAddress::kEmpty, "CIG Create",
+ base::StringPrintf("cig_id:0x%02x, size: %d", cig_id,
+ static_cast<int>(cig_params.cis_cfgs.size())));
}
void reconfigure_cig(uint8_t cig_id,
@@ -188,6 +203,12 @@
STREAM_TO_UINT8(evt.status, stream);
STREAM_TO_UINT8(evt.cig_id, stream);
+ BTM_LogHistory(
+ kBtmLogTag, RawAddress::kEmpty, "CIG Remove complete",
+ base::StringPrintf(
+ "cig_id:0x%02x, status: %s", evt.cig_id,
+ hci_status_code_text((tHCI_STATUS)(evt.status)).c_str()));
+
if (evt.status == HCI_SUCCESS) {
auto cis_it = conn_hdl_to_cis_map_.cbegin();
while (cis_it != conn_hdl_to_cis_map_.cend()) {
@@ -210,6 +231,8 @@
btsnd_hcic_remove_cig(cig_id, base::BindOnce(&iso_impl::on_remove_cig,
base::Unretained(this)));
+ BTM_LogHistory(kBtmLogTag, RawAddress::kEmpty, "CIG Remove",
+ base::StringPrintf("cig_id:0x%02x (f:%d)", cig_id, force));
}
void on_status_establish_cis(
@@ -234,6 +257,14 @@
evt.cig_id = 0xFF;
cis->state_flags &= ~kStateFlagIsConnecting;
cig_callbacks_->OnCisEvent(kIsoEventCisEstablishCmpl, &evt);
+
+ BTM_LogHistory(
+ kBtmLogTag, cis_hdl_to_addr[evt.cis_conn_hdl],
+ "Establish CIS failed ",
+ base::StringPrintf(
+ "handle:0x%04x, status: %s", evt.cis_conn_hdl,
+ hci_status_code_text((tHCI_STATUS)(status)).c_str()));
+ cis_hdl_to_addr.erase(evt.cis_conn_hdl);
}
}
}
@@ -246,6 +277,13 @@
(kStateFlagIsConnected | kStateFlagIsConnecting)))
<< "Already connected or connecting";
cis->state_flags |= kStateFlagIsConnecting;
+
+ tBTM_SEC_DEV_REC* p_rec = btm_find_dev_by_handle(el.acl_conn_handle);
+ if (p_rec) {
+ cis_hdl_to_addr[el.cis_conn_handle] = p_rec->ble.pseudo_addr;
+ BTM_LogHistory(kBtmLogTag, p_rec->ble.pseudo_addr, "Establish CIS",
+ base::StringPrintf("handle:0x%04x", el.acl_conn_handle));
+ }
}
btsnd_hcic_create_cis(conn_params.conn_pairs.size(),
conn_params.conn_pairs.data(),
@@ -261,6 +299,11 @@
<< "Not connected";
bluetooth::legacy::hci::GetInterface().Disconnect(
cis_handle, static_cast<tHCI_STATUS>(reason));
+
+ BTM_LogHistory(kBtmLogTag, cis_hdl_to_addr[cis_handle], "Disconnect CIS ",
+ base::StringPrintf(
+ "handle:0x%04x, reason:%s", cis_handle,
+ hci_reason_code_text((tHCI_REASON)(reason)).c_str()));
}
void on_setup_iso_data_path(uint8_t* stream, uint16_t len) {
@@ -278,6 +321,12 @@
return;
}
+ BTM_LogHistory(kBtmLogTag, cis_hdl_to_addr[conn_handle],
+ "Setup data path complete",
+ base::StringPrintf(
+ "handle:0x%04x, status:%s", conn_handle,
+ hci_status_code_text((tHCI_STATUS)(status)).c_str()));
+
if (status == HCI_SUCCESS) iso->state_flags |= kStateFlagHasDataPathSet;
if (iso->state_flags & kStateFlagIsBroadcast) {
LOG_ASSERT(big_callbacks_ != nullptr) << "Invalid BIG callbacks";
@@ -306,12 +355,22 @@
std::move(path_params.codec_conf),
base::BindOnce(&iso_impl::on_setup_iso_data_path,
base::Unretained(this)));
+ BTM_LogHistory(
+ kBtmLogTag, cis_hdl_to_addr[conn_handle], "Setup data path",
+ base::StringPrintf(
+ "handle:0x%04x, dir:0x%02x, path_id:0x%02x, codec_id:0x%02x",
+ conn_handle, path_params.data_path_dir, path_params.data_path_id,
+ path_params.codec_id_format));
}
void on_remove_iso_data_path(uint8_t* stream, uint16_t len) {
uint8_t status;
uint16_t conn_handle;
+ if (len < 3) {
+ LOG(WARNING) << __func__ << "Malformatted packet received";
+ return;
+ }
STREAM_TO_UINT8(status, stream);
STREAM_TO_UINT16(conn_handle, stream);
@@ -323,6 +382,12 @@
return;
}
+ BTM_LogHistory(kBtmLogTag, cis_hdl_to_addr[conn_handle],
+ "Remove data path complete",
+ base::StringPrintf(
+ "handle:0x%04x, status:%s", conn_handle,
+ hci_status_code_text((tHCI_STATUS)(status)).c_str()));
+
if (status == HCI_SUCCESS) iso->state_flags &= ~kStateFlagHasDataPathSet;
if (iso->state_flags & kStateFlagIsBroadcast) {
@@ -345,6 +410,9 @@
iso_handle, data_path_dir,
base::BindOnce(&iso_impl::on_remove_iso_data_path,
base::Unretained(this)));
+ BTM_LogHistory(kBtmLogTag, cis_hdl_to_addr[iso_handle], "Remove data path",
+ base::StringPrintf("handle:0x%04x, dir:0x%02x", iso_handle,
+ data_path_dir));
}
void on_iso_link_quality_read(uint8_t* stream, uint16_t len) {
@@ -358,6 +426,13 @@
uint32_t rxUnreceivedPackets;
uint32_t duplicatePackets;
+ // 1 + 2 + 4 * 7
+#define ISO_LINK_QUALITY_SIZE 31
+ if (len < ISO_LINK_QUALITY_SIZE) {
+ LOG(ERROR) << "Malformated link quality format, len=" << len;
+ return;
+ }
+
STREAM_TO_UINT8(status, stream);
if (status != HCI_SUCCESS) {
LOG(ERROR) << "Failed to Read ISO Link Quality, status: "
@@ -491,6 +566,12 @@
auto cis = GetCisIfKnown(evt.cis_conn_hdl);
LOG_ASSERT(cis != nullptr) << "No such cis: " << +evt.cis_conn_hdl;
+ BTM_LogHistory(kBtmLogTag, cis_hdl_to_addr[evt.cis_conn_hdl],
+ "CIS established event",
+ base::StringPrintf(
+ "cis_handle:0x%04x status:%s", evt.cis_conn_hdl,
+ hci_error_code_text((tHCI_STATUS)(evt.status)).c_str()));
+
cis->sync_info.first_sync_ts = bluetooth::common::time_get_os_boottime_us();
STREAM_TO_UINT24(evt.cig_sync_delay, data);
@@ -508,7 +589,11 @@
STREAM_TO_UINT16(evt.max_pdu_stom, data);
STREAM_TO_UINT16(evt.iso_itv, data);
- if (evt.status == HCI_SUCCESS) cis->state_flags |= kStateFlagIsConnected;
+ if (evt.status == HCI_SUCCESS) {
+ cis->state_flags |= kStateFlagIsConnected;
+ } else {
+ cis_hdl_to_addr.erase(evt.cis_conn_hdl);
+ }
cis->state_flags &= ~kStateFlagIsConnecting;
@@ -524,6 +609,13 @@
LOG_ASSERT(cig_callbacks_ != nullptr) << "Invalid CIG callbacks";
LOG_INFO("%s flags: %d", __func__, +cis->state_flags);
+
+ BTM_LogHistory(
+ kBtmLogTag, cis_hdl_to_addr[handle], "CIS disconnected",
+ base::StringPrintf("cis_handle:0x%04x, reason:%s", handle,
+ hci_error_code_text((tHCI_REASON)(reason)).c_str()));
+ cis_hdl_to_addr.erase(handle);
+
if (cis->state_flags & kStateFlagIsConnected) {
cis_disconnected_evt evt = {
.reason = reason,
@@ -870,6 +962,7 @@
std::map<uint16_t, std::unique_ptr<iso_cis>> conn_hdl_to_cis_map_;
std::map<uint16_t, std::unique_ptr<iso_bis>> conn_hdl_to_bis_map_;
+ std::map<uint16_t, RawAddress> cis_hdl_to_addr;
std::atomic_uint16_t iso_credits_;
uint16_t iso_buffer_size_;
diff --git a/system/stack/btm/btm_sec.cc b/system/stack/btm/btm_sec.cc
index 333adbf..df9b6cc 100644
--- a/system/stack/btm/btm_sec.cc
+++ b/system/stack/btm/btm_sec.cc
@@ -44,6 +44,7 @@
#include "osi/include/compat.h"
#include "osi/include/log.h"
#include "osi/include/osi.h"
+#include "osi/include/properties.h"
#include "stack/btm/btm_dev.h"
#include "stack/btm/security_device_record.h"
#include "stack/eatt/eatt.h"
@@ -153,6 +154,14 @@
}
}
+static bool concurrentPeerAuthIsEnabled() {
+ // Was previously named BTM_DISABLE_CONCURRENT_PEER_AUTH.
+ // Renamed to ENABLED for homogeneity with system properties
+ static const bool sCONCURRENT_PEER_AUTH_IS_ENABLED = osi_property_get_bool(
+ "bluetooth.btm.sec.concurrent_peer_auth.enabled", true);
+ return sCONCURRENT_PEER_AUTH_IS_ENABLED;
+}
+
void NotifyBondingCanceled(tBTM_STATUS btm_status) {
if (btm_cb.api.p_bond_cancel_cmpl_callback) {
btm_cb.api.p_bond_cancel_cmpl_callback(BTM_SUCCESS);
@@ -2046,8 +2055,14 @@
* Returns void
*
******************************************************************************/
-void btm_create_conn_cancel_complete(const uint8_t* p) {
+void btm_create_conn_cancel_complete(const uint8_t* p, uint16_t evt_len) {
uint8_t status;
+
+ if (evt_len < 1 + BD_ADDR_LEN) {
+ BTM_TRACE_ERROR("%s malformatted event packet, too short", __func__);
+ return;
+ }
+
STREAM_TO_UINT8(status, p);
RawAddress bd_addr;
STREAM_TO_BDADDR(bd_addr, p);
@@ -2986,13 +3001,23 @@
* Returns void
*
******************************************************************************/
-void btm_read_local_oob_complete(uint8_t* p) {
+void btm_read_local_oob_complete(uint8_t* p, uint16_t evt_len) {
tBTM_SP_LOC_OOB evt_data;
- uint8_t status = *p++;
+ uint8_t status;
+ if (evt_len < 1) {
+ goto err_out;
+ }
+
+ STREAM_TO_UINT8(status, p);
BTM_TRACE_EVENT("btm_read_local_oob_complete:%d", status);
if (status == HCI_SUCCESS) {
evt_data.status = BTM_SUCCESS;
+
+ if (evt_len < 1 + 32) {
+ goto err_out;
+ }
+
STREAM_TO_ARRAY16(evt_data.c.data(), p);
STREAM_TO_ARRAY16(evt_data.r.data(), p);
} else
@@ -3003,6 +3028,11 @@
btm_sp_evt_data.loc_oob = evt_data;
(*btm_cb.api.p_sp_callback)(BTM_SP_LOC_OOB_EVT, &btm_sp_evt_data);
}
+
+ return;
+
+err_out:
+ BTM_TRACE_ERROR("%s malformatted event packet, too short", __func__);
}
/*******************************************************************************
@@ -3385,11 +3415,9 @@
__func__, p_dev_rec, p_dev_rec->p_callback);
p_dev_rec->p_callback = NULL;
l2cu_resubmit_pending_sec_req(&p_dev_rec->bd_addr);
-#ifdef BTM_DISABLE_CONCURRENT_PEER_AUTH
- } else if (BTM_DISABLE_CONCURRENT_PEER_AUTH &&
+ } else if (!concurrentPeerAuthIsEnabled() &&
p_dev_rec->sec_state == BTM_SEC_STATE_AUTHENTICATING) {
p_dev_rec->sec_state = BTM_SEC_STATE_IDLE;
-#endif
}
return;
}
@@ -4014,10 +4042,9 @@
tBTM_SEC_DEV_REC* p_dev_rec = btm_find_or_alloc_dev(bda);
VLOG(2) << __func__ << " bda: " << bda;
-#if (defined(BTM_DISABLE_CONCURRENT_PEER_AUTH) && \
- (BTM_DISABLE_CONCURRENT_PEER_AUTH == TRUE))
- p_dev_rec->sec_state = BTM_SEC_STATE_AUTHENTICATING;
-#endif
+ if (!concurrentPeerAuthIsEnabled()) {
+ p_dev_rec->sec_state = BTM_SEC_STATE_AUTHENTICATING;
+ }
if ((btm_cb.pairing_state == BTM_PAIR_STATE_WAIT_PIN_REQ) &&
(btm_cb.collision_start_time != 0) &&
@@ -4477,12 +4504,16 @@
static void btm_sec_wait_and_start_authentication(tBTM_SEC_DEV_REC* p_dev_rec) {
p_dev_rec->sec_state = BTM_SEC_STATE_AUTHENTICATING;
auto addr = new RawAddress(p_dev_rec->bd_addr);
+
+ static const int32_t delay_auth =
+ osi_property_get_int32("bluetooth.btm.sec.delay_auth_ms.value", 0);
+
bt_status_t status = do_in_main_thread_delayed(
FROM_HERE, base::Bind(&btm_sec_auth_timer_timeout, addr),
#if BASE_VER < 931007
- base::TimeDelta::FromMilliseconds(BTM_DELAY_AUTH_MS));
+ base::TimeDelta::FromMilliseconds(delay_auth));
#else
- base::Milliseconds(BTM_DELAY_AUTH_MS));
+ base::Milliseconds(delay_auth));
#endif
if (status != BT_STATUS_SUCCESS) {
LOG(ERROR) << __func__
diff --git a/system/stack/btm/btm_sec.h b/system/stack/btm/btm_sec.h
index 8b92d5d..0acb6ca 100644
--- a/system/stack/btm/btm_sec.h
+++ b/system/stack/btm/btm_sec.h
@@ -454,7 +454,7 @@
* Returns void
*
******************************************************************************/
-void btm_create_conn_cancel_complete(const uint8_t* p);
+void btm_create_conn_cancel_complete(const uint8_t* p, uint16_t evt_len);
/*******************************************************************************
*
@@ -582,7 +582,7 @@
* Returns void
*
******************************************************************************/
-void btm_read_local_oob_complete(uint8_t* p);
+void btm_read_local_oob_complete(uint8_t* p, uint16_t evt_len);
/*******************************************************************************
*
diff --git a/system/stack/btu/btu_hcif.cc b/system/stack/btu/btu_hcif.cc
index 3c0f5d0..ec05df2 100644
--- a/system/stack/btu/btu_hcif.cc
+++ b/system/stack/btu/btu_hcif.cc
@@ -93,10 +93,10 @@
static void btu_hcif_io_cap_request_evt(const uint8_t* p);
static void btu_ble_ll_conn_param_upd_evt(uint8_t* p, uint16_t evt_len);
-static void btu_ble_proc_ltk_req(uint8_t* p);
+static void btu_ble_proc_ltk_req(uint8_t* p, uint16_t evt_len);
static void btu_hcif_encryption_key_refresh_cmpl_evt(uint8_t* p);
static void btu_ble_data_length_change_evt(uint8_t* p, uint16_t evt_len);
-static void btu_ble_rc_param_req_evt(uint8_t* p);
+static void btu_ble_rc_param_req_evt(uint8_t* p, uint8_t len);
/**
* Log HCI event metrics that are not handled in special functions
@@ -340,16 +340,16 @@
btm_ble_process_adv_pkt(ble_evt_len, p);
break;
case HCI_BLE_LL_CONN_PARAM_UPD_EVT:
- btu_ble_ll_conn_param_upd_evt(p, hci_evt_len);
+ btu_ble_ll_conn_param_upd_evt(p, ble_evt_len);
break;
case HCI_BLE_READ_REMOTE_FEAT_CMPL_EVT:
- btm_ble_read_remote_features_complete(p);
+ btm_ble_read_remote_features_complete(p, ble_evt_len);
break;
case HCI_BLE_LTK_REQ_EVT: /* received only at peripheral device */
- btu_ble_proc_ltk_req(p);
+ btu_ble_proc_ltk_req(p, ble_evt_len);
break;
case HCI_BLE_RC_PARAM_REQ_EVT:
- btu_ble_rc_param_req_evt(p);
+ btu_ble_rc_param_req_evt(p, ble_evt_len);
break;
case HCI_BLE_DATA_LENGTH_CHANGE_EVT:
btu_ble_data_length_change_evt(p, hci_evt_len);
@@ -1185,7 +1185,7 @@
break;
case HCI_DELETE_STORED_LINK_KEY:
- btm_delete_stored_link_key_complete(p);
+ btm_delete_stored_link_key_complete(p, evt_len);
break;
case HCI_READ_LOCAL_NAME:
@@ -1193,11 +1193,11 @@
break;
case HCI_GET_LINK_QUALITY:
- btm_read_link_quality_complete(p);
+ btm_read_link_quality_complete(p, evt_len);
break;
case HCI_READ_RSSI:
- btm_read_rssi_complete(p);
+ btm_read_rssi_complete(p, evt_len);
break;
case HCI_READ_FAILED_CONTACT_COUNTER:
@@ -1209,15 +1209,15 @@
break;
case HCI_READ_TRANSMIT_POWER_LEVEL:
- btm_read_tx_power_complete(p, false);
+ btm_read_tx_power_complete(p, evt_len, false);
break;
case HCI_CREATE_CONNECTION_CANCEL:
- btm_create_conn_cancel_complete(p);
+ btm_create_conn_cancel_complete(p, evt_len);
break;
case HCI_READ_LOCAL_OOB_DATA:
- btm_read_local_oob_complete(p);
+ btm_read_local_oob_complete(p, evt_len);
break;
case HCI_READ_INQ_TX_POWER_LEVEL:
@@ -1226,15 +1226,15 @@
/* BLE Commands sComplete*/
case HCI_BLE_RAND:
case HCI_BLE_ENCRYPT:
- btm_ble_rand_enc_complete(p, opcode, (tBTM_RAND_ENC_CB*)p_cplt_cback);
+ btm_ble_rand_enc_complete(p, evt_len, opcode, (tBTM_RAND_ENC_CB*)p_cplt_cback);
break;
case HCI_BLE_READ_ADV_CHNL_TX_POWER:
- btm_read_tx_power_complete(p, true);
+ btm_read_tx_power_complete(p, evt_len, true);
break;
case HCI_BLE_WRITE_ADV_ENABLE:
- btm_ble_write_adv_enable_complete(p);
+ btm_ble_write_adv_enable_complete(p, evt_len);
break;
case HCI_BLE_CREATE_LL_CONN:
@@ -1645,6 +1645,11 @@
uint16_t latency;
uint16_t timeout;
+ if (evt_len < 9) {
+ LOG_ERROR("Bogus event packet, too short");
+ return;
+ }
+
STREAM_TO_UINT8(status, p);
STREAM_TO_UINT16(handle, p);
STREAM_TO_UINT16(interval, p);
@@ -1655,10 +1660,22 @@
interval, latency, timeout);
}
-static void btu_ble_proc_ltk_req(uint8_t* p) {
+static void btu_ble_proc_ltk_req(uint8_t* p, uint16_t evt_len) {
uint16_t ediv, handle;
uint8_t* pp;
+ // following the spec in Core_v5.3/Vol 4/Part E
+ // / 7.7.65.5 LE Long Term Key Request event
+ // A BLE Long Term Key Request event contains:
+ // - 1-byte subevent (already consumed in btu_hcif_process_event)
+ // - 2-byte connection handler
+ // - 8-byte random number
+ // - 2 byte Encrypted_Diversifier
+ if (evt_len < 2 + 8 + 2) {
+ LOG_ERROR("Event packet too short");
+ return;
+ }
+
STREAM_TO_UINT16(handle, p);
pp = p + 8;
STREAM_TO_UINT16(ediv, pp);
@@ -1687,10 +1704,15 @@
/**********************************************
* End of BLE Events Handler
**********************************************/
-static void btu_ble_rc_param_req_evt(uint8_t* p) {
+static void btu_ble_rc_param_req_evt(uint8_t* p, uint8_t len) {
uint16_t handle;
uint16_t int_min, int_max, latency, timeout;
+ if (len < 10) {
+ LOG(ERROR) << __func__ << "bogus event packet, too short";
+ return;
+ }
+
STREAM_TO_UINT16(handle, p);
STREAM_TO_UINT16(int_min, p);
STREAM_TO_UINT16(int_max, p);
diff --git a/system/stack/eatt/eatt_impl.h b/system/stack/eatt/eatt_impl.h
index 8293bb6..998fc10 100644
--- a/system/stack/eatt/eatt_impl.h
+++ b/system/stack/eatt/eatt_impl.h
@@ -153,7 +153,8 @@
tL2CAP_LE_CFG_INFO local_coc_cfg = {
.mtu = eatt_dev->rx_mtu_,
.mps = eatt_dev->rx_mps_ < max_mps ? eatt_dev->rx_mps_ : max_mps,
- .credits = L2CAP_LE_CREDIT_DEFAULT};
+ .credits = L2CA_LeCreditDefault(),
+ };
if (!L2CA_ConnectCreditBasedRsp(bda, identifier, lcids, L2CAP_CONN_OK,
&local_coc_cfg))
@@ -584,7 +585,7 @@
tL2CAP_LE_CFG_INFO local_coc_cfg = {
.mtu = eatt_dev->rx_mtu_,
.mps = eatt_dev->rx_mps_,
- .credits = L2CAP_LE_CREDIT_DEFAULT,
+ .credits = L2CA_LeCreditDefault(),
.number_of_channels = num_of_channels,
};
@@ -874,7 +875,12 @@
<< " is_eatt_supported = " << int(is_eatt_supported);
if (!is_eatt_supported) return;
- eatt_device* eatt_dev = add_eatt_device(bd_addr);
+ eatt_device* eatt_dev = this->find_device_by_address(bd_addr);
+ if (!eatt_dev) {
+ LOG(INFO) << __func__ << " Adding device: " << bd_addr
+ << " on supported features callback.";
+ eatt_dev = add_eatt_device(bd_addr);
+ }
if (role != HCI_ROLE_CENTRAL) {
/* TODO For now do nothing, we could run a timer here and start EATT if
diff --git a/system/stack/gap/gap_conn.cc b/system/stack/gap/gap_conn.cc
index d1b8219..23d9589 100644
--- a/system/stack/gap/gap_conn.cc
+++ b/system/stack/gap/gap_conn.cc
@@ -207,7 +207,7 @@
/* Configure L2CAP COC, if transport is LE */
if (transport == BT_TRANSPORT_LE) {
- p_ccb->local_coc_cfg.credits = L2CAP_LE_CREDIT_DEFAULT;
+ p_ccb->local_coc_cfg.credits = L2CA_LeCreditDefault();
p_ccb->local_coc_cfg.mtu = p_cfg->mtu;
uint16_t max_mps = controller_get_interface()->get_acl_data_size_ble();
diff --git a/system/stack/gatt/connection_manager.cc b/system/stack/gatt/connection_manager.cc
index d832369..b2f48ff 100644
--- a/system/stack/gatt/connection_manager.cc
+++ b/system/stack/gatt/connection_manager.cc
@@ -29,6 +29,7 @@
#include "bind_helpers.h"
#include "internal_include/bt_trace.h"
+#include "main/shim/dumpsys.h"
#include "main/shim/le_scanning_manager.h"
#include "main/shim/shim.h"
#include "osi/include/alarm.h"
@@ -121,12 +122,11 @@
bool IsTargetedAnnouncement(const uint8_t* p_eir, uint16_t eir_len) {
const uint8_t* p_service_data = p_eir;
- uint16_t remaining_data_len = eir_len;
uint8_t service_data_len = 0;
while ((p_service_data = AdvertiseDataParser::GetFieldByType(
p_service_data + service_data_len,
- (remaining_data_len -= service_data_len),
+ eir_len - (p_service_data - p_eir) - service_data_len,
BTM_BLE_AD_TYPE_SERVICE_DATA_TYPE, &service_data_len))) {
uint16_t uuid;
uint8_t announcement_type;
@@ -174,8 +174,6 @@
LOG_INFO("Found targeted announcement for device %s",
addr.ToString().c_str());
- BTM_LogHistory(kBtmLogTag, addr, "Found TA from");
-
if (it->second.is_in_accept_list) {
LOG_INFO("Device %s is already connecting", addr.ToString().c_str());
return;
@@ -186,6 +184,8 @@
return;
}
+ BTM_LogHistory(kBtmLogTag, addr, "Found TA from");
+
/* Take fist app_id and use it for direct_connect */
auto app_id = *(it->second.doing_targeted_announcements_conn.begin());
@@ -397,6 +397,10 @@
return true;
}
+bool is_background_connection(const RawAddress& address) {
+ return bgconn_dev.find(address) != bgconn_dev.end();
+}
+
/** deregister all related background connetion device. */
void on_app_deregistered(uint8_t app_id) {
LOG_DEBUG("app_id=%d", static_cast<int>(app_id));
@@ -527,13 +531,15 @@
address.ToString().c_str());
auto it = bgconn_dev.find(address);
if (it == bgconn_dev.end()) {
- LOG_WARN("Unable to find background connection to remove");
+ LOG_WARN("Unable to find background connection to remove peer:%s",
+ PRIVATE_ADDRESS(address));
return false;
}
auto app_it = it->second.doing_direct_conn.find(app_id);
if (app_it == it->second.doing_direct_conn.end()) {
- LOG_WARN("Unable to find direct connection to remove");
+ LOG_WARN("Unable to find direct connection to remove peer:%s",
+ PRIVATE_ADDRESS(address));
return false;
}
diff --git a/system/stack/gatt/connection_manager.h b/system/stack/gatt/connection_manager.h
index 0dff252..0c407c8 100644
--- a/system/stack/gatt/connection_manager.h
+++ b/system/stack/gatt/connection_manager.h
@@ -61,4 +61,6 @@
extern void on_connection_timed_out(uint8_t app_id, const RawAddress& address);
extern void on_connection_timed_out_from_shim(const RawAddress& address);
+extern bool is_background_connection(const RawAddress& address);
+
} // namespace connection_manager
diff --git a/system/stack/gatt/gatt_api.cc b/system/stack/gatt/gatt_api.cc
index 3861016..9a1152b 100644
--- a/system/stack/gatt/gatt_api.cc
+++ b/system/stack/gatt/gatt_api.cc
@@ -21,7 +21,7 @@
* this file contains GATT interface functions
*
******************************************************************************/
-#include "gatt_api.h"
+#include "stack/include/gatt_api.h"
#include <base/logging.h>
#include <base/strings/string_number_conversions.h>
@@ -31,13 +31,16 @@
#include "bt_target.h"
#include "device/include/controller.h"
-#include "gatt_int.h"
+#include "gd/os/system_properties.h"
#include "internal_include/stack_config.h"
#include "l2c_api.h"
#include "main/shim/dumpsys.h"
#include "osi/include/allocator.h"
+#include "osi/include/list.h"
#include "osi/include/log.h"
+#include "stack/btm/btm_dev.h"
#include "stack/gatt/connection_manager.h"
+#include "stack/gatt/gatt_int.h"
#include "stack/include/bt_hdr.h"
#include "types/bluetooth/uuid.h"
#include "types/bt_transport.h"
@@ -304,7 +307,7 @@
elem.type = list.asgn_range.is_primary ? GATT_UUID_PRI_SERVICE
: GATT_UUID_SEC_SERVICE;
- if (elem.type == GATT_UUID_PRI_SERVICE) {
+ if (elem.type == GATT_UUID_PRI_SERVICE && gatt_cb.over_br_enabled) {
Uuid* p_uuid = gatts_get_service_uuid(elem.p_db);
if (*p_uuid != Uuid::From16Bit(UUID_SERVCLASS_GMCS_SERVER) &&
*p_uuid != Uuid::From16Bit(UUID_SERVCLASS_GTBS_SERVER)) {
@@ -1479,3 +1482,48 @@
LOG_DEBUG("status=%d", status);
return status;
}
+
+static void gatt_bonded_check_add_address(const RawAddress& bda) {
+ if (!gatt_is_bda_in_the_srv_chg_clt_list(bda)) {
+ gatt_add_a_bonded_dev_for_srv_chg(bda);
+ }
+}
+
+std::optional<bool> OVERRIDE_GATT_LOAD_BONDED = std::nullopt;
+
+static bool gatt_load_bonded_is_enabled() {
+ static const bool sGATT_LOAD_BONDED = bluetooth::os::GetSystemPropertyBool(
+ "bluetooth.gatt.load_bonded.enabled", false);
+ if (OVERRIDE_GATT_LOAD_BONDED.has_value()) {
+ return OVERRIDE_GATT_LOAD_BONDED.value();
+ }
+ return sGATT_LOAD_BONDED;
+}
+
+/* Initialize GATTS list of bonded device service change updates.
+ *
+ * Addresses for bonded devices (publict for BR/EDR or pseudo for BLE) are added
+ * to GATTS service change control list so that updates are sent to bonded
+ * devices on next connect after any handles for GATTS services change due to
+ * services added/removed.
+ */
+void gatt_load_bonded(void) {
+ const bool load_bonded = gatt_load_bonded_is_enabled();
+ LOG_INFO("load bonded: %s", load_bonded ? "True" : "False");
+ if (!load_bonded) {
+ return;
+ }
+ for (tBTM_SEC_DEV_REC* p_dev_rec : btm_get_sec_dev_rec()) {
+ if (p_dev_rec->is_link_key_known()) {
+ LOG_VERBOSE("Add bonded BR/EDR transport %s",
+ PRIVATE_ADDRESS(p_dev_rec->bd_addr));
+ gatt_bonded_check_add_address(p_dev_rec->bd_addr);
+ }
+ if (p_dev_rec->is_le_link_key_known()) {
+ VLOG(1) << " add bonded BLE " << p_dev_rec->ble.pseudo_addr;
+ LOG_VERBOSE("Add bonded BLE %s",
+ PRIVATE_ADDRESS(p_dev_rec->ble.pseudo_addr));
+ gatt_bonded_check_add_address(p_dev_rec->ble.pseudo_addr);
+ }
+ }
+}
diff --git a/system/stack/gatt/gatt_auth.cc b/system/stack/gatt/gatt_auth.cc
index 600829a..2329bc5 100644
--- a/system/stack/gatt/gatt_auth.cc
+++ b/system/stack/gatt/gatt_auth.cc
@@ -176,23 +176,26 @@
tGATT_CLCB* p_clcb = p_tcb->pending_enc_clcb.front();
p_tcb->pending_enc_clcb.pop_front();
- bool status = false;
- if (result == BTM_SUCCESS) {
- if (gatt_get_sec_act(p_tcb) == GATT_SEC_ENCRYPT_MITM) {
- status = BTM_IsLinkKeyAuthed(*bd_addr, transport);
- } else {
- status = true;
+ if (p_clcb != NULL) {
+ bool status = false;
+ if (result == BTM_SUCCESS) {
+ if (gatt_get_sec_act(p_tcb) == GATT_SEC_ENCRYPT_MITM) {
+ status = BTM_IsLinkKeyAuthed(*bd_addr, transport);
+ } else {
+ status = true;
+ }
}
- }
- gatt_sec_check_complete(status, p_clcb, p_tcb->sec_act);
+ gatt_sec_check_complete(status, p_clcb, p_tcb->sec_act);
+ }
/* start all other pending operation in queue */
std::deque<tGATT_CLCB*> new_pending_clcbs;
while (!p_tcb->pending_enc_clcb.empty()) {
tGATT_CLCB* p_clcb = p_tcb->pending_enc_clcb.front();
p_tcb->pending_enc_clcb.pop_front();
- if (gatt_security_check_start(p_clcb)) new_pending_clcbs.push_back(p_clcb);
+ if (p_clcb != NULL && gatt_security_check_start(p_clcb))
+ new_pending_clcbs.push_back(p_clcb);
}
p_tcb->pending_enc_clcb = new_pending_clcbs;
}
@@ -229,7 +232,7 @@
while (!p_tcb->pending_enc_clcb.empty()) {
tGATT_CLCB* p_clcb = p_tcb->pending_enc_clcb.front();
p_tcb->pending_enc_clcb.pop_front();
- if (gatt_security_check_start(p_clcb))
+ if (p_clcb != NULL && gatt_security_check_start(p_clcb))
new_pending_clcbs.push_back(p_clcb);
}
p_tcb->pending_enc_clcb = new_pending_clcbs;
diff --git a/system/stack/gatt/gatt_cl.cc b/system/stack/gatt/gatt_cl.cc
index e2f8808..d026633 100644
--- a/system/stack/gatt/gatt_cl.cc
+++ b/system/stack/gatt/gatt_cl.cc
@@ -595,7 +595,8 @@
VLOG(1) << StringPrintf("value resp op_code = %s len = %d",
gatt_dbg_op_name(op_code), len);
- if (len < GATT_PREP_WRITE_RSP_MIN_LEN) {
+ if (len < GATT_PREP_WRITE_RSP_MIN_LEN ||
+ len > GATT_PREP_WRITE_RSP_MIN_LEN + sizeof(value.value)) {
LOG(ERROR) << "illegal prepare write response length, discard";
gatt_end_operation(p_clcb, GATT_INVALID_PDU, &value);
return;
@@ -604,7 +605,7 @@
STREAM_TO_UINT16(value.handle, p);
STREAM_TO_UINT16(value.offset, p);
- value.len = len - 4;
+ value.len = len - GATT_PREP_WRITE_RSP_MIN_LEN;
memcpy(value.value, p, value.len);
diff --git a/system/stack/gatt/gatt_int.h b/system/stack/gatt/gatt_int.h
index a05c27c..fa173f5 100644
--- a/system/stack/gatt/gatt_int.h
+++ b/system/stack/gatt/gatt_int.h
@@ -259,6 +259,8 @@
}
#undef CASE_RETURN_TEXT
+// If you change these values make sure to look at b/262219144 before.
+// Some platform rely on this to never changes
#define GATT_GATT_START_HANDLE 1
#define GATT_GAP_START_HANDLE 20
#define GATT_GMCS_START_HANDLE 40
@@ -266,14 +268,6 @@
#define GATT_TMAS_START_HANDLE 130
#define GATT_APP_START_HANDLE 134
-#ifndef GATT_DEFAULT_START_HANDLE
-#define GATT_DEFAULT_START_HANDLE GATT_GATT_START_HANDLE
-#endif
-
-#ifndef GATT_LAST_HANDLE
-#define GATT_LAST_HANDLE 0xFFFF
-#endif
-
typedef struct hdl_cfg {
uint16_t gatt_start_hdl;
uint16_t gap_start_hdl;
@@ -452,6 +446,7 @@
tGATT_APPL_INFO cb_info;
tGATT_HDL_CFG hdl_cfg;
+ bool over_br_enabled;
} tGATT_CB;
#define GATT_SIZE_OF_SRV_CHG_HNDL_RANGE 4
diff --git a/system/stack/gatt/gatt_main.cc b/system/stack/gatt/gatt_main.cc
index 09eccb6..b9d1120 100644
--- a/system/stack/gatt/gatt_main.cc
+++ b/system/stack/gatt/gatt_main.cc
@@ -123,10 +123,10 @@
L2CA_RegisterFixedChannel(L2CAP_ATT_CID, &fixed_reg);
- bool gatt_over_br_is_disabled =
- osi_property_get_bool("bluetooth.gatt_over_bredr.disabled", false);
+ gatt_cb.over_br_enabled =
+ osi_property_get_bool("bluetooth.gatt.over_bredr.enabled", true);
/* Now, register with L2CAP for ATT PSM over BR/EDR */
- if (!gatt_over_br_is_disabled &&
+ if (gatt_cb.over_br_enabled &&
!L2CA_Register2(BT_PSM_ATT, dyn_info, false /* enable_snoop */, nullptr,
GATT_MAX_MTU_SIZE, 0, BTM_SEC_NONE)) {
LOG(ERROR) << "ATT Dynamic Registration failed";
@@ -909,6 +909,13 @@
/** This function is called to send a service chnaged indication to the
* specified bd address */
void gatt_send_srv_chg_ind(const RawAddress& peer_bda) {
+ static const uint16_t sGATT_DEFAULT_START_HANDLE =
+ (uint16_t)osi_property_get_int32(
+ "bluetooth.gatt.default_start_handle_for_srvc_change.value",
+ GATT_GATT_START_HANDLE);
+ static const uint16_t sGATT_LAST_HANDLE = (uint16_t)osi_property_get_int32(
+ "bluetooth.gatt.last_handle_for_srvc_change.value", 0xFFFF);
+
VLOG(1) << __func__;
if (!gatt_cb.handle_of_h_r) return;
@@ -921,8 +928,8 @@
uint8_t handle_range[GATT_SIZE_OF_SRV_CHG_HNDL_RANGE];
uint8_t* p = handle_range;
- UINT16_TO_STREAM(p, GATT_DEFAULT_START_HANDLE);
- UINT16_TO_STREAM(p, GATT_LAST_HANDLE);
+ UINT16_TO_STREAM(p, sGATT_DEFAULT_START_HANDLE);
+ UINT16_TO_STREAM(p, sGATT_LAST_HANDLE);
GATTS_HandleValueIndication(conn_id, gatt_cb.handle_of_h_r,
GATT_SIZE_OF_SRV_CHG_HNDL_RANGE, handle_range);
}
diff --git a/system/stack/gatt/gatt_utils.cc b/system/stack/gatt/gatt_utils.cc
index ec19948..d65b6d6 100644
--- a/system/stack/gatt/gatt_utils.cc
+++ b/system/stack/gatt/gatt_utils.cc
@@ -1163,13 +1163,13 @@
uint16_t cid = p_clcb->cid;
if (!p_tcb->pending_enc_clcb.empty()) {
- auto iter = std::find_if(p_tcb->pending_enc_clcb.begin(),
- p_tcb->pending_enc_clcb.end(),
- [p_clcb](auto& el) { return el == p_clcb; });
- if (iter != p_tcb->pending_enc_clcb.end()) {
- p_tcb->pending_enc_clcb.erase(iter);
- LOG_WARN("Removing clcb (%p) for conn id=0x%04x from pending_enc_clcb",
- p_clcb, p_clcb->conn_id);
+ for (size_t i = 0; i < p_tcb->pending_enc_clcb.size(); i++) {
+ if (p_tcb->pending_enc_clcb.at(i) == p_clcb) {
+ LOG_WARN("Removing clcb (%p) for conn id=0x%04x from pending_enc_clcb",
+ p_clcb, p_clcb->conn_id);
+ p_tcb->pending_enc_clcb.at(i) = NULL;
+ break;
+ }
}
}
@@ -1466,11 +1466,18 @@
}
if (!connection_manager::direct_connect_remove(gatt_if, bda)) {
- BTM_AcceptlistRemove(bda);
- LOG_INFO(
- "GATT connection manager has no record but removed filter acceptlist "
- "gatt_if:%hhu peer:%s",
- gatt_if, PRIVATE_ADDRESS(bda));
+ if (!connection_manager::is_background_connection(bda)) {
+ BTM_AcceptlistRemove(bda);
+ LOG_INFO(
+ "Gatt connection manager has no background record but "
+ " removed filter acceptlist gatt_if:%hhu peer:%s",
+ gatt_if, PRIVATE_ADDRESS(bda));
+ } else {
+ LOG_INFO(
+ "Gatt connection manager maintains a background record"
+ " preserving filter acceptlist gatt_if:%hhu peer:%s",
+ gatt_if, PRIVATE_ADDRESS(bda));
+ }
}
return true;
}
@@ -1708,10 +1715,12 @@
pseduo_op_code_idx = 0x15; /* just an index to op_code_name */
}
- if (pseduo_op_code_idx <= GATT_OP_CODE_MAX)
+ #define ARR_SIZE(a) (sizeof(a)/sizeof(a[0]))
+ if (pseduo_op_code_idx < ARR_SIZE(op_code_name))
return (uint8_t*)op_code_name[pseduo_op_code_idx];
else
return (uint8_t*)"Op Code Exceed Max";
+ #undef ARR_SIZE
}
/** Remove the application interface for the specified background device */
diff --git a/system/stack/hid/hidd_conn.cc b/system/stack/hid/hidd_conn.cc
index da78d40..fc0a245 100644
--- a/system/stack/hid/hidd_conn.cc
+++ b/system/stack/hid/hidd_conn.cc
@@ -27,6 +27,7 @@
#include "bta/include/bta_api.h"
#include "btif/include/btif_hd.h"
+#include "gd/common/init_flags.h"
#include "osi/include/allocator.h"
#include "stack/hid/hidd_int.h"
#include "stack/include/bt_hdr.h"
@@ -347,7 +348,6 @@
static void hidd_l2cif_disconnect(uint16_t cid) {
L2CA_DisconnectReq(cid);
-
HIDD_TRACE_EVENT("%s: cid=%04x", __func__, cid);
tHID_CONN* p_hcon = &hd_cb.device.conn;
@@ -365,6 +365,10 @@
// now disconnect CTRL
L2CA_DisconnectReq(p_hcon->ctrl_cid);
+ if (bluetooth::common::init_flags::
+ clear_hidd_interrupt_cid_on_disconnect_is_enabled()) {
+ p_hcon->ctrl_cid = 0;
+ }
}
if ((p_hcon->ctrl_cid == 0) && (p_hcon->intr_cid == 0)) {
diff --git a/system/stack/include/acl_hci_link_interface.h b/system/stack/include/acl_hci_link_interface.h
index f109b56..5ce61b7 100644
--- a/system/stack/include/acl_hci_link_interface.h
+++ b/system/stack/include/acl_hci_link_interface.h
@@ -52,7 +52,7 @@
void btm_pm_proc_ssr_evt(uint8_t* p, uint16_t evt_len);
void btm_read_automatic_flush_timeout_complete(uint8_t* p);
void btm_read_failed_contact_counter_complete(uint8_t* p);
-void btm_read_link_quality_complete(uint8_t* p);
+void btm_read_link_quality_complete(uint8_t* p, uint16_t evt_len);
void btm_read_remote_ext_features_complete_raw(uint8_t* p, uint8_t evt_len);
void btm_read_remote_ext_features_complete(uint16_t handle, uint8_t page_num,
uint8_t max_page, uint8_t* features);
@@ -62,8 +62,8 @@
uint8_t lmp_version,
uint16_t manufacturer,
uint16_t lmp_subversion);
-void btm_read_rssi_complete(uint8_t* p);
-void btm_read_tx_power_complete(uint8_t* p, bool is_ble);
+void btm_read_rssi_complete(uint8_t* p, uint16_t evt_len);
+void btm_read_tx_power_complete(uint8_t* p, uint16_t evt_len, bool is_ble);
void acl_rcv_acl_data(BT_HDR* p_msg);
void acl_link_segments_xmitted(BT_HDR* p_msg);
diff --git a/system/stack/include/avrc_api.h b/system/stack/include/avrc_api.h
index 877dafe..10a34d1 100644
--- a/system/stack/include/avrc_api.h
+++ b/system/stack/include/avrc_api.h
@@ -236,6 +236,16 @@
/*****************************************************************************
* external function declarations
****************************************************************************/
+/******************************************************************************
+ *
+ * Function avrcp_absolute_volume_is_enabled
+ *
+ * Description Check if config support advance control (absolute volume)
+ *
+ * Returns return true if absolute_volume is enabled
+ *
+ *****************************************************************************/
+bool avrcp_absolute_volume_is_enabled();
/******************************************************************************
*
diff --git a/system/stack/include/ble_hci_link_interface.h b/system/stack/include/ble_hci_link_interface.h
index d4f6d35..ab60b48 100644
--- a/system/stack/include/ble_hci_link_interface.h
+++ b/system/stack/include/ble_hci_link_interface.h
@@ -27,14 +27,15 @@
void btm_ble_process_adv_pkt(uint8_t len, const uint8_t* p);
void btm_ble_process_ext_adv_pkt(uint8_t len, const uint8_t* p);
void btm_ble_process_phy_update_pkt(uint8_t len, uint8_t* p);
-void btm_ble_read_remote_features_complete(uint8_t* p);
+void btm_ble_read_remote_features_complete(uint8_t* p, uint8_t length);
void btm_le_on_advertising_set_terminated(uint8_t* p, uint16_t length);
-extern void btm_ble_write_adv_enable_complete(uint8_t* p);
+extern void btm_ble_write_adv_enable_complete(uint8_t* p, uint16_t evt_len);
extern void btm_ble_create_ll_conn_complete(tHCI_STATUS status);
extern void btm_ble_ltk_request(uint16_t handle, uint8_t rand[8],
uint16_t ediv);
extern void btm_ble_test_command_complete(uint8_t* p);
-extern void btm_ble_rand_enc_complete(uint8_t* p, uint16_t op_code,
+extern void btm_ble_rand_enc_complete(uint8_t* p, uint16_t evt_len,
+ uint16_t op_code,
tBTM_RAND_ENC_CB* p_enc_cplt_cback);
extern bool btm_identity_addr_to_random_pseudo(RawAddress* bd_addr,
tBLE_ADDR_TYPE* p_addr_type,
diff --git a/system/stack/include/dev_hci_link_interface.h b/system/stack/include/dev_hci_link_interface.h
index 9a6a767..b6e3d71 100644
--- a/system/stack/include/dev_hci_link_interface.h
+++ b/system/stack/include/dev_hci_link_interface.h
@@ -23,7 +23,7 @@
#include "types/raw_address.h"
-extern void btm_delete_stored_link_key_complete(uint8_t* p);
+extern void btm_delete_stored_link_key_complete(uint8_t* p, uint16_t evt_len);
extern void btm_vendor_specific_evt(const uint8_t* p, uint8_t evt_len);
extern void btm_vsc_complete(uint8_t* p, uint16_t cc_opcode, uint16_t evt_len,
tBTM_VSC_CMPL_CB* p_vsc_cplt_cback);
diff --git a/system/stack/include/gatt_api.h b/system/stack/include/gatt_api.h
index 05d4115..eb37910 100644
--- a/system/stack/include/gatt_api.h
+++ b/system/stack/include/gatt_api.h
@@ -1190,4 +1190,7 @@
* true, as there is no need to wipe controller acceptlist in this case. */
extern void gatt_reset_bgdev_list(bool after_reset);
+// Initialize GATTS list of bonded device service change updates.
+extern void gatt_load_bonded(void);
+
#endif /* GATT_API_H */
diff --git a/system/stack/include/l2c_api.h b/system/stack/include/l2c_api.h
index d62da3c..c4e452f 100644
--- a/system/stack/include/l2c_api.h
+++ b/system/stack/include/l2c_api.h
@@ -180,14 +180,11 @@
// This is initial amout of credits we send, and amount to which we increase
// credits once they fall below threshold
-constexpr uint16_t L2CAP_LE_CREDIT_DEFAULT = 0xffff;
+uint16_t L2CA_LeCreditDefault();
// If credit count on remote fall below this value, we send back credits to
// reach default value.
-constexpr uint16_t L2CAP_LE_CREDIT_THRESHOLD = 0x0040;
-
-static_assert(L2CAP_LE_CREDIT_THRESHOLD < L2CAP_LE_CREDIT_DEFAULT,
- "Threshold must be smaller than default credits");
+uint16_t L2CA_LeCreditThreshold();
// Max number of CIDs in the L2CAP CREDIT BASED CONNECTION REQUEST
constexpr uint16_t L2CAP_CREDIT_BASED_MAX_CIDS = 5;
@@ -199,7 +196,7 @@
uint16_t result; /* Only used in confirm messages */
uint16_t mtu = 100;
uint16_t mps = 100;
- uint16_t credits = L2CAP_LE_CREDIT_DEFAULT;
+ uint16_t credits = L2CA_LeCreditDefault();
uint8_t number_of_channels = L2CAP_CREDIT_BASED_MAX_CIDS;
};
diff --git a/system/stack/include/sec_hci_link_interface.h b/system/stack/include/sec_hci_link_interface.h
index b5dda7f..dce3c47 100644
--- a/system/stack/include/sec_hci_link_interface.h
+++ b/system/stack/include/sec_hci_link_interface.h
@@ -26,12 +26,12 @@
// This header contains functions for HCIF-Security Management to invoke
//
-void btm_create_conn_cancel_complete(const uint8_t* p);
+void btm_create_conn_cancel_complete(const uint8_t* p, uint16_t evt_len);
void btm_io_capabilities_req(const RawAddress& p);
void btm_io_capabilities_rsp(const uint8_t* p);
void btm_proc_sp_req_evt(tBTM_SP_EVT event, const uint8_t* p);
void btm_read_inq_tx_power_complete(uint8_t* p);
-void btm_read_local_oob_complete(uint8_t* p);
+void btm_read_local_oob_complete(uint8_t* p, uint16_t evt_len);
void btm_rem_oob_req(const uint8_t* p);
void btm_sec_auth_complete(uint16_t handle, tHCI_STATUS status);
void btm_sec_disconnected(uint16_t handle, tHCI_STATUS reason, std::string);
diff --git a/system/stack/include/smp_api.h b/system/stack/include/smp_api.h
index 8124211..cf23914 100644
--- a/system/stack/include/smp_api.h
+++ b/system/stack/include/smp_api.h
@@ -187,8 +187,11 @@
* Description This function is called to generate a public key to be
* passed to a remote device via an Out of Band transport
*
+ * Returns true if the request is successfully sent and executed by the
+ * state machine, false otherwise
+ *
******************************************************************************/
-extern void SMP_CrLocScOobData();
+extern bool SMP_CrLocScOobData();
/*******************************************************************************
*
diff --git a/system/stack/include/stack_metrics_logging.h b/system/stack/include/stack_metrics_logging.h
index 158c98f..58d869f 100644
--- a/system/stack/include/stack_metrics_logging.h
+++ b/system/stack/include/stack_metrics_logging.h
@@ -34,9 +34,9 @@
uint32_t hci_cmd, uint16_t hci_event, uint16_t hci_ble_event,
uint16_t cmd_status, uint16_t reason_code);
-void log_smp_pairing_event(const RawAddress& address, uint8_t smp_cmd,
+void log_smp_pairing_event(const RawAddress& address, uint16_t smp_cmd,
android::bluetooth::DirectionEnum direction,
- uint8_t smp_fail_reason);
+ uint16_t smp_fail_reason);
void log_sdp_attribute(const RawAddress& address, uint16_t protocol_uuid,
uint16_t attribute_id, size_t attribute_size,
diff --git a/system/stack/l2cap/l2c_api.cc b/system/stack/l2cap/l2c_api.cc
index 5464cb4..2a7efd8 100644
--- a/system/stack/l2cap/l2c_api.cc
+++ b/system/stack/l2cap/l2c_api.cc
@@ -34,6 +34,7 @@
#include "device/include/controller.h" // TODO Remove
#include "gd/common/init_flags.h"
+#include "gd/os/system_properties.h"
#include "main/shim/shim.h"
#include "osi/include/allocator.h"
#include "osi/include/log.h"
@@ -65,6 +66,29 @@
return ret;
}
+uint16_t L2CA_LeCreditDefault() {
+ static const uint16_t sL2CAP_LE_CREDIT_DEFAULT =
+ bluetooth::os::GetSystemPropertyUint32Base(
+ "bluetooth.l2cap.le.credit_default.value", 0xffff);
+ return sL2CAP_LE_CREDIT_DEFAULT;
+}
+
+uint16_t L2CA_LeCreditThreshold() {
+ static const uint16_t sL2CAP_LE_CREDIT_THRESHOLD =
+ bluetooth::os::GetSystemPropertyUint32Base(
+ "bluetooth.l2cap.le.credit_threshold.value", 0x0040);
+ return sL2CAP_LE_CREDIT_THRESHOLD;
+}
+
+static bool check_l2cap_credit() {
+ CHECK(L2CA_LeCreditThreshold() < L2CA_LeCreditDefault())
+ << "Threshold must be smaller than default credits";
+ return true;
+}
+
+// Replace static assert with startup assert depending of the config
+static const bool enforce_assert = check_l2cap_credit();
+
/*******************************************************************************
*
* Function L2CA_Register
diff --git a/system/stack/l2cap/l2c_ble.cc b/system/stack/l2cap/l2c_ble.cc
index 607950c..3d66846 100755
--- a/system/stack/l2cap/l2c_ble.cc
+++ b/system/stack/l2cap/l2c_ble.cc
@@ -989,7 +989,8 @@
p_ccb->local_conn_cfg.mtu = L2CAP_SDU_LENGTH_LE_MAX;
p_ccb->local_conn_cfg.mps =
controller_get_interface()->get_acl_data_size_ble();
- p_ccb->local_conn_cfg.credits = L2CAP_LE_CREDIT_DEFAULT,
+ p_ccb->local_conn_cfg.credits = L2CA_LeCreditDefault();
+ p_ccb->remote_credit_count = L2CA_LeCreditDefault();
p_ccb->peer_conn_cfg.mtu = mtu;
p_ccb->peer_conn_cfg.mps = mps;
diff --git a/system/stack/l2cap/l2c_fcr.cc b/system/stack/l2cap/l2c_fcr.cc
index 9c76005..d6d06d9 100644
--- a/system/stack/l2cap/l2c_fcr.cc
+++ b/system/stack/l2cap/l2c_fcr.cc
@@ -734,6 +734,10 @@
} else {
p_data = p_ccb->ble_sdu;
+ if (p_data == NULL) {
+ osi_free(p_buf);
+ return;
+ }
if (p_buf->len > (p_ccb->ble_sdu_length - p_data->len)) {
L2CAP_TRACE_ERROR("%s: buffer length=%d too big. max=%d. Dropped",
__func__, p_data->len,
diff --git a/system/stack/l2cap/l2c_main.cc b/system/stack/l2cap/l2c_main.cc
index 06714cb..1d412f7 100644
--- a/system/stack/l2cap/l2c_main.cc
+++ b/system/stack/l2cap/l2c_main.cc
@@ -221,9 +221,9 @@
--p_ccb->remote_credit_count;
/* If the credits left on the remote device are getting low, send some */
- if (p_ccb->remote_credit_count <= L2CAP_LE_CREDIT_THRESHOLD) {
- uint16_t credits = L2CAP_LE_CREDIT_DEFAULT - p_ccb->remote_credit_count;
- p_ccb->remote_credit_count = L2CAP_LE_CREDIT_DEFAULT;
+ if (p_ccb->remote_credit_count <= L2CA_LeCreditThreshold()) {
+ uint16_t credits = L2CA_LeCreditDefault() - p_ccb->remote_credit_count;
+ p_ccb->remote_credit_count = L2CA_LeCreditDefault();
/* Return back credits */
l2c_csm_execute(p_ccb, L2CEVT_L2CA_SEND_FLOW_CONTROL_CREDIT, &credits);
diff --git a/system/stack/metrics/stack_metrics_logging.cc b/system/stack/metrics/stack_metrics_logging.cc
index 4e23bd2..d69d54e 100644
--- a/system/stack/metrics/stack_metrics_logging.cc
+++ b/system/stack/metrics/stack_metrics_logging.cc
@@ -42,9 +42,9 @@
hci_ble_event, cmd_status, reason_code);
}
-void log_smp_pairing_event(const RawAddress& address, uint8_t smp_cmd,
+void log_smp_pairing_event(const RawAddress& address, uint16_t smp_cmd,
android::bluetooth::DirectionEnum direction,
- uint8_t smp_fail_reason) {
+ uint16_t smp_fail_reason) {
bluetooth::shim::LogMetricSmpPairingEvent(address, smp_cmd, direction,
smp_fail_reason);
}
diff --git a/system/stack/sdp/sdp_db.cc b/system/stack/sdp/sdp_db.cc
index 297b312..acef4a5 100644
--- a/system/stack/sdp/sdp_db.cc
+++ b/system/stack/sdp/sdp_db.cc
@@ -355,6 +355,11 @@
uint16_t xx, yy, zz;
tSDP_RECORD* p_rec = &sdp_cb.server_db.record[0];
+ if (p_val == nullptr) {
+ SDP_TRACE_WARNING("Trying to add attribute with p_val == nullptr, skipped");
+ return (false);
+ }
+
if (sdp_cb.trace_level >= BT_TRACE_LEVEL_DEBUG) {
if ((attr_type == UINT_DESC_TYPE) ||
(attr_type == TWO_COMP_INT_DESC_TYPE) ||
@@ -402,6 +407,13 @@
if (p_rec->record_handle == handle) {
tSDP_ATTRIBUTE* p_attr = &p_rec->attribute[0];
+ // error out early, no need to look up
+ if (p_rec->free_pad_ptr >= SDP_MAX_PAD_LEN) {
+ SDP_TRACE_ERROR("the free pad for SDP record with handle %d is "
+ "full, skip adding the attribute", handle);
+ return (false);
+ }
+
/* Found the record. Now, see if the attribute already exists */
for (xx = 0; xx < p_rec->num_attributes; xx++, p_attr++) {
/* The attribute exists. replace it */
@@ -440,15 +452,13 @@
attr_len = 0;
}
- if ((attr_len > 0) && (p_val != 0)) {
+ if (attr_len > 0) {
p_attr->len = attr_len;
memcpy(&p_rec->attr_pad[p_rec->free_pad_ptr], p_val, (size_t)attr_len);
p_attr->value_ptr = &p_rec->attr_pad[p_rec->free_pad_ptr];
p_rec->free_pad_ptr += attr_len;
- } else if ((attr_len == 0 &&
- p_attr->len !=
- 0) || /* if truncate to 0 length, simply don't add */
- p_val == 0) {
+ } else if (attr_len == 0 && p_attr->len != 0) {
+ /* if truncate to 0 length, simply don't add */
SDP_TRACE_ERROR(
"SDP_AddAttribute fail, length exceed maximum: ID %d: attr_len:%d ",
attr_id, attr_len);
diff --git a/system/stack/sdp/sdp_discovery.cc b/system/stack/sdp/sdp_discovery.cc
index 22d6e7c..9988c54 100644
--- a/system/stack/sdp/sdp_discovery.cc
+++ b/system/stack/sdp/sdp_discovery.cc
@@ -337,7 +337,7 @@
uint8_t* p_end;
uint8_t type;
- if (p_ccb->p_db->raw_data) {
+ if (p_ccb->p_db && p_ccb->p_db->raw_data) {
cpy_len = p_ccb->p_db->raw_size - p_ccb->p_db->raw_used;
list_len = p_ccb->list_len;
p = &p_ccb->rsp_list[0];
diff --git a/system/stack/smp/smp_act.cc b/system/stack/smp/smp_act.cc
index dad83c0..3c8218e 100644
--- a/system/stack/smp/smp_act.cc
+++ b/system/stack/smp/smp_act.cc
@@ -1042,7 +1042,7 @@
tBTM_LE_KEY_VALUE pid_key = {
.pid_key = {},
};
- ;
+
STREAM_TO_UINT8(pid_key.pid_key.identity_addr_type, p);
STREAM_TO_BDADDR(pid_key.pid_key.identity_addr, p);
pid_key.pid_key.irk = p_cb->tk;
diff --git a/system/stack/smp/smp_api.cc b/system/stack/smp/smp_api.cc
index 05e68db..9355168 100644
--- a/system/stack/smp/smp_api.cc
+++ b/system/stack/smp/smp_api.cc
@@ -567,10 +567,13 @@
* Description This function is called to generate a public key to be
* passed to a remote device via Out of Band transport.
*
+ * Returns true if the request is successfully sent and executed by the
+ * state machine, false otherwise
+ *
******************************************************************************/
-void SMP_CrLocScOobData() {
+bool SMP_CrLocScOobData() {
tSMP_INT_DATA smp_int_data;
- smp_sm_event(&smp_cb, SMP_CR_LOC_SC_OOB_DATA_EVT, &smp_int_data);
+ return smp_sm_event(&smp_cb, SMP_CR_LOC_SC_OOB_DATA_EVT, &smp_int_data);
}
/*******************************************************************************
diff --git a/system/stack/smp/smp_int.h b/system/stack/smp/smp_int.h
index c4ddf3e..5e73180 100644
--- a/system/stack/smp/smp_int.h
+++ b/system/stack/smp/smp_int.h
@@ -32,6 +32,15 @@
#include "stack/include/bt_octets.h"
#include "types/raw_address.h"
+typedef enum : uint16_t {
+ SMP_METRIC_COMMAND_LE_FLAG = 0x0000,
+ SMP_METRIC_COMMAND_BR_FLAG = 0x0100,
+ SMP_METRIC_COMMAND_LE_PAIRING_CMPL = 0xFF00,
+ SMP_METRIC_COMMAND_BR_PAIRING_CMPL = 0xFF01,
+} tSMP_METRIC_COMMAND;
+
+constexpr uint16_t SMP_METRIC_STATUS_INTERNAL_FLAG = 0x0100;
+
typedef enum : uint8_t {
/* Legacy mode */
SMP_MODEL_ENCRYPTION_ONLY = 0, /* Just Works model */
@@ -312,7 +321,7 @@
extern void smp_init(void);
/* smp main */
-extern void smp_sm_event(tSMP_CB* p_cb, tSMP_EVENT event,
+extern bool smp_sm_event(tSMP_CB* p_cb, tSMP_EVENT event,
tSMP_INT_DATA* p_data);
extern tSMP_STATE smp_get_state(void);
@@ -414,7 +423,8 @@
/* smp_util.cc */
extern void smp_log_metrics(const RawAddress& bd_addr, bool is_outgoing,
- const uint8_t* p_buf, size_t buf_len);
+ const uint8_t* p_buf, size_t buf_len,
+ bool is_over_br);
extern bool smp_send_cmd(uint8_t cmd_code, tSMP_CB* p_cb);
extern void smp_cb_cleanup(tSMP_CB* p_cb);
extern void smp_reset_control_value(tSMP_CB* p_cb);
diff --git a/system/stack/smp/smp_l2c.cc b/system/stack/smp/smp_l2c.cc
index 6a13e8a..264cdc3 100644
--- a/system/stack/smp/smp_l2c.cc
+++ b/system/stack/smp/smp_l2c.cc
@@ -195,7 +195,8 @@
smp_rsp_timeout, NULL);
smp_log_metrics(p_cb->pairing_bda, false /* incoming */,
- p_buf->data + p_buf->offset, p_buf->len);
+ p_buf->data + p_buf->offset, p_buf->len,
+ false /* is_over_br */);
if (cmd == SMP_OPCODE_CONFIRM) {
SMP_TRACE_DEBUG(
@@ -334,7 +335,8 @@
smp_rsp_timeout, NULL);
smp_log_metrics(p_cb->pairing_bda, false /* incoming */,
- p_buf->data + p_buf->offset, p_buf->len);
+ p_buf->data + p_buf->offset, p_buf->len,
+ true /* is_over_br */);
p_cb->rcvd_cmd_code = cmd;
p_cb->rcvd_cmd_len = (uint8_t)p_buf->len;
diff --git a/system/stack/smp/smp_main.cc b/system/stack/smp/smp_main.cc
index 3953949..ab30510 100644
--- a/system/stack/smp/smp_main.cc
+++ b/system/stack/smp/smp_main.cc
@@ -973,17 +973,19 @@
* not NULL state, adjust the new state to the returned state. If
* (api_evt != MAX), call callback function.
*
- * Returns void.
+ * Returns true if the event is executed and a state transition can be
+ * expected, false if the event is ignored, state is invalid, or
+ * the role is invalid for the control block.
*
******************************************************************************/
-void smp_sm_event(tSMP_CB* p_cb, tSMP_EVENT event, tSMP_INT_DATA* p_data) {
+bool smp_sm_event(tSMP_CB* p_cb, tSMP_EVENT event, tSMP_INT_DATA* p_data) {
uint8_t curr_state = p_cb->state;
tSMP_SM_TBL state_table;
uint8_t action, entry, i;
if (p_cb->role >= 2) {
SMP_TRACE_DEBUG("Invalid role: %d", p_cb->role);
- return;
+ return false;
}
tSMP_ENTRY_TBL entry_table = smp_entry_table[p_cb->role];
@@ -991,7 +993,7 @@
SMP_TRACE_EVENT("main smp_sm_event");
if (curr_state >= SMP_STATE_MAX) {
SMP_TRACE_DEBUG("Invalid state: %d", curr_state);
- return;
+ return false;
}
SMP_TRACE_DEBUG("SMP Role: %s State: [%s (%d)], Event: [%s (%d)]",
@@ -1014,7 +1016,7 @@
SMP_TRACE_DEBUG("Ignore event [%s (%d)] in state [%s (%d)]",
smp_get_event_name(event), event,
smp_get_state_name(curr_state), curr_state);
- return;
+ return false;
}
/* Get possible next state from state table. */
@@ -1035,6 +1037,7 @@
}
}
SMP_TRACE_DEBUG("result state = %s", smp_get_state_name(p_cb->state));
+ return true;
}
/*******************************************************************************
diff --git a/system/stack/smp/smp_utils.cc b/system/stack/smp/smp_utils.cc
index 22fcb71..f2aba5e 100644
--- a/system/stack/smp/smp_utils.cc
+++ b/system/stack/smp/smp_utils.cc
@@ -41,6 +41,8 @@
#include "stack/include/stack_metrics_logging.h"
#include "types/raw_address.h"
+void btm_dev_consolidate_existing_connections(const RawAddress& bd_addr);
+
#define SMP_PAIRING_REQ_SIZE 7
#define SMP_CONFIRM_CMD_SIZE (OCTET16_LEN + 1)
#define SMP_RAND_CMD_SIZE (OCTET16_LEN + 1)
@@ -314,22 +316,26 @@
* @param buf_len length available to read for p_buf
*/
void smp_log_metrics(const RawAddress& bd_addr, bool is_outgoing,
- const uint8_t* p_buf, size_t buf_len) {
+ const uint8_t* p_buf, size_t buf_len, bool is_over_br) {
if (buf_len < 1) {
LOG(WARNING) << __func__ << ": buffer is too small, size is " << buf_len;
return;
}
- uint8_t cmd;
- STREAM_TO_UINT8(cmd, p_buf);
+ uint8_t raw_cmd;
+ STREAM_TO_UINT8(raw_cmd, p_buf);
buf_len--;
uint8_t failure_reason = 0;
- if (cmd == SMP_OPCODE_PAIRING_FAILED && buf_len >= 1) {
+ if (raw_cmd == SMP_OPCODE_PAIRING_FAILED && buf_len >= 1) {
STREAM_TO_UINT8(failure_reason, p_buf);
}
+ uint16_t metric_cmd =
+ is_over_br ? SMP_METRIC_COMMAND_BR_FLAG : SMP_METRIC_COMMAND_LE_FLAG;
+ metric_cmd |= static_cast<uint16_t>(raw_cmd);
android::bluetooth::DirectionEnum direction =
is_outgoing ? android::bluetooth::DirectionEnum::DIRECTION_OUTGOING
: android::bluetooth::DirectionEnum::DIRECTION_INCOMING;
- log_smp_pairing_event(bd_addr, cmd, direction, failure_reason);
+ log_smp_pairing_event(bd_addr, metric_cmd, direction,
+ static_cast<uint16_t>(failure_reason));
}
/*******************************************************************************
@@ -350,7 +356,8 @@
SMP_TRACE_EVENT("%s", __func__);
smp_log_metrics(rem_bda, true /* outgoing */,
- p_toL2CAP->data + p_toL2CAP->offset, p_toL2CAP->len);
+ p_toL2CAP->data + p_toL2CAP->offset, p_toL2CAP->len,
+ smp_cb.smp_over_br /* is_over_br */);
l2cap_ret = L2CA_SendFixedChnlData(fixed_cid, rem_bda, p_toL2CAP);
if (l2cap_ret == L2CAP_DW_FAILED) {
@@ -978,6 +985,27 @@
smp_status_text(evt_data.cmplt.reason).c_str()));
}
+ // Log pairing complete event
+ {
+ auto direction =
+ p_cb->flags & SMP_PAIR_FLAGS_WE_STARTED_DD
+ ? android::bluetooth::DirectionEnum::DIRECTION_OUTGOING
+ : android::bluetooth::DirectionEnum::DIRECTION_INCOMING;
+ uint16_t metric_cmd = p_cb->smp_over_br
+ ? SMP_METRIC_COMMAND_BR_PAIRING_CMPL
+ : SMP_METRIC_COMMAND_LE_PAIRING_CMPL;
+ uint16_t metric_status = p_cb->status;
+ if (metric_status > SMP_MAX_FAIL_RSN_PER_SPEC) {
+ metric_status |= SMP_METRIC_STATUS_INTERNAL_FLAG;
+ }
+ log_smp_pairing_event(p_cb->pairing_bda, metric_cmd, direction,
+ metric_status);
+ }
+
+ if (p_cb->status == SMP_SUCCESS && p_cb->smp_over_br) {
+ btm_dev_consolidate_existing_connections(pairing_bda);
+ }
+
smp_reset_control_value(p_cb);
if (p_callback) (*p_callback)(SMP_COMPLT_EVT, pairing_bda, &evt_data);
diff --git a/system/stack/test/ad_parser_unittest.cc b/system/stack/test/ad_parser_unittest.cc
index 36cc7f2..1f6eb29 100644
--- a/system/stack/test/ad_parser_unittest.cc
+++ b/system/stack/test/ad_parser_unittest.cc
@@ -174,4 +174,40 @@
glued.insert(glued.end(), scan_resp.begin(), scan_resp.end());
EXPECT_TRUE(AdvertiseDataParser::IsValid(glued));
+}
+
+TEST(AdvertiseDataParserTest, GetFieldByTypeInLoop) {
+ // Single field.
+ const uint8_t AD_TYPE_SVC_DATA = 0x16;
+ const std::vector<uint8_t> data0{
+ 0x02, 0x01, 0x02,
+ 0x07, 0x2e, 0x6a, 0xc1, 0x19, 0x52, 0x1e, 0x49,
+ 0x09, 0x16, 0x4e, 0x18, 0x00, 0xff, 0x0f, 0x03, 0x00, 0x00,
+ 0x02, 0x0a, 0x7f,
+ 0x03, 0x16, 0x4f, 0x18,
+ 0x04, 0x16, 0x53, 0x18, 0x00,
+ 0x0f, 0x09, 0x48, 0x5f, 0x43, 0x33, 0x45, 0x41, 0x31, 0x36, 0x33, 0x46, 0x35, 0x36, 0x34, 0x46 };
+
+ const uint8_t* p_service_data = data0.data();
+ uint8_t service_data_len = 0;
+
+ int match_no = 0;
+ while ((p_service_data = AdvertiseDataParser::GetFieldByType(
+ p_service_data + service_data_len,
+ data0.size() - (p_service_data - data0.data()) - service_data_len,
+ AD_TYPE_SVC_DATA, &service_data_len))) {
+ auto position = (p_service_data - data0.data());
+ if (match_no == 0) {
+ EXPECT_EQ(position, 13);
+ EXPECT_EQ(service_data_len, 8);
+ } else if (match_no == 1) {
+ EXPECT_EQ(position, 26);
+ EXPECT_EQ(service_data_len, 2);
+ } else if (match_no == 2) {
+ EXPECT_EQ(position, 30);
+ EXPECT_EQ(service_data_len, 3);
+ }
+ match_no++;
+ }
+ EXPECT_EQ(match_no, 3);
}
\ No newline at end of file
diff --git a/system/stack/test/btm/stack_btm_regression_tests.cc b/system/stack/test/btm/stack_btm_regression_tests.cc
new file mode 100644
index 0000000..93c3d68
--- /dev/null
+++ b/system/stack/test/btm/stack_btm_regression_tests.cc
@@ -0,0 +1,51 @@
+/*
+ *
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include <gtest/gtest.h>
+
+#include "bt_hdr.h"
+#include "btm_ble_api_types.h"
+#include "hci_error_code.h"
+#include "osi/include/allocator.h"
+#include "ble_hci_link_interface.h"
+
+namespace {
+
+class StackBTMRegressionTests : public ::testing::Test {
+ protected:
+ void SetUp() override {}
+ void TearDown() override {}
+};
+
+// regression test for b/260078907
+TEST_F(StackBTMRegressionTests,
+ OOB_in_btm_ble_add_resolving_list_entry_complete) {
+ BT_HDR* pevent = (BT_HDR*)osi_calloc(sizeof(BT_HDR));
+ btm_ble_add_resolving_list_entry_complete(pevent->data, 0);
+ osi_free(pevent);
+}
+
+// regression test for b/255304475
+TEST_F(StackBTMRegressionTests,
+ OOB_in_btm_ble_clear_resolving_list_complete) {
+ BT_HDR* pevent = (BT_HDR*)osi_calloc(sizeof(BT_HDR));
+ btm_ble_clear_resolving_list_complete(pevent->data, 0);
+ osi_free(pevent);
+}
+
+} // namespace
diff --git a/system/stack/test/btm/stack_btm_test.cc b/system/stack/test/btm/stack_btm_test.cc
index 49ae48c..e170664 100644
--- a/system/stack/test/btm/stack_btm_test.cc
+++ b/system/stack/test/btm/stack_btm_test.cc
@@ -129,8 +129,6 @@
return &mock_stack_config;
}
-std::map<std::string, int> mock_function_count_map;
-
namespace {
using testing::_;
diff --git a/system/stack/test/btm_iso_test.cc b/system/stack/test/btm_iso_test.cc
index a5554a0..5085918 100644
--- a/system/stack/test/btm_iso_test.cc
+++ b/system/stack/test/btm_iso_test.cc
@@ -24,6 +24,7 @@
#include "mock_controller.h"
#include "mock_hcic_layer.h"
#include "osi/include/allocator.h"
+#include "stack/btm/btm_dev.h"
#include "stack/include/bt_hdr.h"
#include "stack/include/hci_error_code.h"
#include "stack/include/hcidefs.h"
@@ -42,6 +43,10 @@
// Iso Manager currently works on top of the legacy HCI layer
bool bluetooth::shim::is_gd_shim_enabled() { return false; }
+tBTM_SEC_DEV_REC* btm_find_dev_by_handle(uint16_t handle) { return nullptr; }
+void BTM_LogHistory(const std::string& tag, const RawAddress& bd_addr,
+ const std::string& msg, const std::string& extra) {}
+
namespace bte {
class BteInterface {
public:
diff --git a/system/stack/test/common/mock_l2cap_layer.cc b/system/stack/test/common/mock_l2cap_layer.cc
index 2892759..d03d143 100644
--- a/system/stack/test/common/mock_l2cap_layer.cc
+++ b/system/stack/test/common/mock_l2cap_layer.cc
@@ -94,3 +94,8 @@
tL2CAP_LE_CFG_INFO* peer_cfg) {
return l2cap_interface->ReconfigCreditBasedConnsReq(bd_addr, lcids, peer_cfg);
}
+uint16_t L2CA_LeCreditDefault() { return l2cap_interface->LeCreditDefault(); }
+
+uint16_t L2CA_LeCreditThreshold() {
+ return l2cap_interface->LeCreditThreshold();
+}
diff --git a/system/stack/test/common/mock_l2cap_layer.h b/system/stack/test/common/mock_l2cap_layer.h
index edb662b..cb2b36d 100644
--- a/system/stack/test/common/mock_l2cap_layer.h
+++ b/system/stack/test/common/mock_l2cap_layer.h
@@ -51,6 +51,8 @@
tL2CAP_LE_CFG_INFO* p_cfg) = 0;
virtual bool ReconfigCreditBasedConnsReq(const RawAddress& bd_addr, std::vector<uint16_t> &lcids,
tL2CAP_LE_CFG_INFO* peer_cfg) = 0;
+ virtual uint16_t LeCreditDefault() = 0;
+ virtual uint16_t LeCreditThreshold() = 0;
virtual ~L2capInterface() = default;
};
@@ -84,6 +86,8 @@
tL2CAP_LE_CFG_INFO* p_cfg));
MOCK_METHOD3(ReconfigCreditBasedConnsReq,
bool(const RawAddress& p_bd_addr, std::vector<uint16_t> &lcids, tL2CAP_LE_CFG_INFO* peer_cfg));
+ MOCK_METHOD(uint16_t, LeCreditDefault, ());
+ MOCK_METHOD(uint16_t, LeCreditThreshold, ());
};
/**
diff --git a/system/stack/test/eatt/eatt_test.cc b/system/stack/test/eatt/eatt_test.cc
index 0d81f80..5bc9ef3 100644
--- a/system/stack/test/eatt/eatt_test.cc
+++ b/system/stack/test/eatt/eatt_test.cc
@@ -120,6 +120,82 @@
ASSERT_TRUE(test_tcb.eatt == num_of_accepted_connections);
}
+ void ConnectDeviceBothSides(int num_of_accepted_connections,
+ std::vector<uint16_t>& incoming_cids) {
+ base::OnceCallback<void(const RawAddress&, uint8_t)> eatt_supp_feat_cb;
+
+ ON_CALL(gatt_interface_, ClientReadSupportedFeatures)
+ .WillByDefault(
+ [&eatt_supp_feat_cb](
+ const RawAddress& addr,
+ base::OnceCallback<void(const RawAddress&, uint8_t)> cb) {
+ eatt_supp_feat_cb = std::move(cb);
+ return true;
+ });
+
+ // Return false to trigger supported features request
+ ON_CALL(gatt_interface_, GetEattSupport)
+ .WillByDefault([](const RawAddress& addr) { return false; });
+
+ std::vector<uint16_t> test_local_cids{61, 62, 63, 64, 65};
+ EXPECT_CALL(l2cap_interface_,
+ ConnectCreditBasedReq(BT_PSM_EATT, test_address, _))
+ .WillOnce(Return(test_local_cids));
+
+ eatt_instance_->Connect(test_address);
+
+ // Let the remote connect while we are trying to connect
+ EXPECT_CALL(
+ l2cap_interface_,
+ ConnectCreditBasedRsp(test_address, 1, incoming_cids, L2CAP_CONN_OK, _))
+ .WillOnce(Return(true));
+ l2cap_app_info_.pL2CA_CreditBasedConnectInd_Cb(
+ test_address, incoming_cids, BT_PSM_EATT, EATT_MIN_MTU_MPS, 1);
+
+ // Respond to feature request scheduled by the connect request
+ ASSERT_TRUE(eatt_supp_feat_cb);
+ if (eatt_supp_feat_cb) {
+ std::move(eatt_supp_feat_cb)
+ .Run(test_address, BLE_GATT_SVR_SUP_FEAT_EATT_BITMASK);
+ }
+
+ int i = 0;
+ for (uint16_t cid : test_local_cids) {
+ EattChannel* channel =
+ eatt_instance_->FindEattChannelByCid(test_address, cid);
+ ASSERT_TRUE(channel != nullptr);
+ ASSERT_TRUE(channel->state_ == EattChannelState::EATT_CHANNEL_PENDING);
+
+ if (i < num_of_accepted_connections) {
+ l2cap_app_info_.pL2CA_CreditBasedConnectCfm_Cb(
+ test_address, cid, EATT_MIN_MTU_MPS, L2CAP_CONN_OK);
+ connected_cids_.push_back(cid);
+
+ ASSERT_TRUE(channel->state_ == EattChannelState::EATT_CHANNEL_OPENED);
+ ASSERT_TRUE(channel->tx_mtu_ == EATT_MIN_MTU_MPS);
+ } else {
+ l2cap_app_info_.pL2CA_Error_Cb(cid, L2CAP_CONN_NO_RESOURCES);
+
+ EattChannel* channel =
+ eatt_instance_->FindEattChannelByCid(test_address, cid);
+ ASSERT_TRUE(channel == nullptr);
+ }
+ i++;
+ }
+
+ // Check the incoming CIDs as well
+ for (auto cid : incoming_cids) {
+ EattChannel* channel =
+ eatt_instance_->FindEattChannelByCid(test_address, cid);
+ ASSERT_NE(nullptr, channel);
+ ASSERT_EQ(channel->state_, EattChannelState::EATT_CHANNEL_OPENED);
+ ASSERT_TRUE(channel->tx_mtu_ == EATT_MIN_MTU_MPS);
+ }
+
+ ASSERT_EQ(test_tcb.eatt,
+ num_of_accepted_connections + incoming_cids.size());
+ }
+
void DisconnectEattByPeer(void) {
for (uint16_t cid : connected_cids_)
l2cap_app_info_.pL2CA_DisconnectInd_Cb(cid, true);
@@ -140,6 +216,9 @@
bluetooth::gatt::SetMockGattInterface(&gatt_interface_);
controller::SetMockControllerInterface(&controller_interface);
+ // Clear the static memory for each test case
+ memset(&test_tcb, 0, sizeof(test_tcb));
+
EXPECT_CALL(l2cap_interface_, RegisterLECoc(BT_PSM_EATT, _, _))
.WillOnce(DoAll(SaveArg<1>(&l2cap_app_info_), Return(BT_PSM_EATT)));
@@ -317,6 +396,22 @@
DisconnectEattDevice(incoming_cids);
}
+TEST_F(EattTest, ConnectInitiatedWhenRemoteConnects) {
+ ON_CALL(btm_api_interface_, IsEncrypted)
+ .WillByDefault(
+ [](const RawAddress& addr, tBT_TRANSPORT transport) { return true; });
+
+ std::vector<uint16_t> incoming_cids{71, 72, 73, 74};
+ ConnectDeviceBothSides(1, incoming_cids);
+
+ std::vector<uint16_t> disconnecting_cids;
+ disconnecting_cids.insert(disconnecting_cids.end(), incoming_cids.begin(),
+ incoming_cids.end());
+ disconnecting_cids.insert(disconnecting_cids.end(), connected_cids_.begin(),
+ connected_cids_.end());
+ DisconnectEattDevice(disconnecting_cids);
+}
+
TEST_F(EattTest, ConnectSucceedMultipleChannels) {
ConnectDeviceEattSupported(5);
DisconnectEattDevice(connected_cids_);
diff --git a/system/stack/test/gatt/gatt_api_test.cc b/system/stack/test/gatt/gatt_api_test.cc
new file mode 100644
index 0000000..7900b77
--- /dev/null
+++ b/system/stack/test/gatt/gatt_api_test.cc
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "gatt_api.h"
+
+#include <base/logging.h>
+#include <gtest/gtest.h>
+
+#include "btm/btm_dev.h"
+#include "gatt/gatt_int.h"
+
+extern tBTM_CB btm_cb;
+
+static const size_t QUEUE_SIZE_MAX = 10;
+
+static tBTM_SEC_DEV_REC* make_bonded_ble_device(const RawAddress& bda,
+ const RawAddress& rra) {
+ tBTM_SEC_DEV_REC* dev = btm_sec_allocate_dev_rec();
+ dev->sec_flags |= BTM_SEC_LE_LINK_KEY_KNOWN;
+ dev->bd_addr = bda;
+ dev->ble.pseudo_addr = rra;
+ dev->ble.key_type = BTM_LE_KEY_PID | BTM_LE_KEY_PENC | BTM_LE_KEY_LENC;
+ return dev;
+}
+
+static tBTM_SEC_DEV_REC* make_bonded_dual_device(const RawAddress& bda,
+ const RawAddress& rra) {
+ tBTM_SEC_DEV_REC* dev = make_bonded_ble_device(bda, rra);
+ dev->sec_flags |= BTM_SEC_LINK_KEY_KNOWN;
+ return dev;
+}
+
+extern std::optional<bool> OVERRIDE_GATT_LOAD_BONDED;
+
+class GattApiTest : public ::testing::Test {
+ protected:
+ GattApiTest() = default;
+
+ virtual ~GattApiTest() = default;
+
+ void SetUp() override {
+ btm_cb.sec_dev_rec = list_new(osi_free);
+ gatt_cb.srv_chg_clt_q = fixed_queue_new(QUEUE_SIZE_MAX);
+ logging::SetMinLogLevel(-2);
+ }
+
+ void TearDown() override { list_free(btm_cb.sec_dev_rec); }
+};
+
+static const RawAddress SAMPLE_PUBLIC_BDA = {
+ {0x00, 0x00, 0x11, 0x22, 0x33, 0x44}};
+
+static const RawAddress SAMPLE_RRA_BDA = {{0xAA, 0xAA, 0x11, 0x22, 0x33, 0x44}};
+
+TEST_F(GattApiTest, test_gatt_load_bonded_ble_only) {
+ OVERRIDE_GATT_LOAD_BONDED = std::optional{true};
+ make_bonded_ble_device(SAMPLE_PUBLIC_BDA, SAMPLE_RRA_BDA);
+
+ gatt_load_bonded();
+
+ ASSERT_TRUE(gatt_is_bda_in_the_srv_chg_clt_list(SAMPLE_RRA_BDA));
+ ASSERT_FALSE(gatt_is_bda_in_the_srv_chg_clt_list(SAMPLE_PUBLIC_BDA));
+ OVERRIDE_GATT_LOAD_BONDED.reset();
+}
+
+TEST_F(GattApiTest, test_gatt_load_bonded_dual) {
+ OVERRIDE_GATT_LOAD_BONDED = std::optional{true};
+ make_bonded_dual_device(SAMPLE_PUBLIC_BDA, SAMPLE_RRA_BDA);
+
+ gatt_load_bonded();
+
+ ASSERT_TRUE(gatt_is_bda_in_the_srv_chg_clt_list(SAMPLE_RRA_BDA));
+ ASSERT_TRUE(gatt_is_bda_in_the_srv_chg_clt_list(SAMPLE_PUBLIC_BDA));
+ OVERRIDE_GATT_LOAD_BONDED.reset();
+}
diff --git a/system/stack/test/gatt/gatt_sr_test.cc b/system/stack/test/gatt/gatt_sr_test.cc
index 7e6ab31..f142bae 100644
--- a/system/stack/test/gatt/gatt_sr_test.cc
+++ b/system/stack/test/gatt/gatt_sr_test.cc
@@ -63,6 +63,8 @@
bool direct_connect_remove(uint8_t app_id, const RawAddress& address) {
return false;
}
+bool is_background_connection(const RawAddress& address) { return false; }
+
} // namespace connection_manager
BT_HDR* attp_build_sr_msg(tGATT_TCB& tcb, uint8_t op_code, tGATT_SR_MSG* p_msg,
diff --git a/system/stack/test/gatt/mock_gatt_utils_ref.cc b/system/stack/test/gatt/mock_gatt_utils_ref.cc
index d416e98..59d0022 100644
--- a/system/stack/test/gatt/mock_gatt_utils_ref.cc
+++ b/system/stack/test/gatt/mock_gatt_utils_ref.cc
@@ -29,6 +29,7 @@
bool direct_connect_remove(uint8_t app_id, const RawAddress& address) {
return false;
}
+bool is_background_connection(const RawAddress& address) { return false; }
} // namespace connection_manager
/** stack/gatt/att_protocol.cc */
diff --git a/system/stack/test/stack_l2cap_test.cc b/system/stack/test/stack_l2cap_test.cc
index 2418772..e8a6f1a 100644
--- a/system/stack/test/stack_l2cap_test.cc
+++ b/system/stack/test/stack_l2cap_test.cc
@@ -17,6 +17,7 @@
#include <gtest/gtest.h>
#include "common/init_flags.h"
+#include "device/include/controller.h"
#include "internal_include/bt_trace.h"
#include "stack/btm/btm_int_types.h"
#include "stack/include/l2cap_hci_link_interface.h"
@@ -26,24 +27,38 @@
tBTM_CB btm_cb;
extern tL2C_CB l2cb;
+void l2c_link_send_to_lower_br_edr(tL2C_LCB* p_lcb, BT_HDR* p_buf);
+void l2c_link_send_to_lower_ble(tL2C_LCB* p_lcb, BT_HDR* p_buf);
+
// Global trace level referred in the code under test
uint8_t appl_trace_level = BT_TRACE_LEVEL_VERBOSE;
extern "C" void LogMsg(uint32_t trace_set_mask, const char* fmt_str, ...) {}
-const char* test_flags[] = {
- "INIT_logging_debug_enabled_for_all=true",
- nullptr,
-};
+namespace {
+constexpr uint16_t kAclBufferCountClassic = 123;
+constexpr uint8_t kAclBufferCountBle = 45;
+
+} // namespace
class StackL2capTest : public ::testing::Test {
protected:
void SetUp() override {
- bluetooth::common::InitFlags::Load(test_flags);
- l2cb = {}; // TODO Use proper init/free APIs
+ bluetooth::common::InitFlags::SetAllForTesting();
+ controller_.get_acl_buffer_count_classic = []() {
+ return kAclBufferCountClassic;
+ };
+ controller_.get_acl_buffer_count_ble = []() { return kAclBufferCountBle; };
+ controller_.supports_ble = []() -> bool { return true; };
+ l2c_init();
}
- void TearDown() override {}
+ void TearDown() override {
+ l2c_free();
+ controller_ = {};
+ }
+
+ controller_t controller_;
};
TEST_F(StackL2capTest, l2cble_process_data_length_change_event) {
@@ -65,3 +80,117 @@
l2cble_process_data_length_change_event(0x1234, 0x001b, 0x001b);
ASSERT_EQ(0x001b, l2cb.lcb_pool[0].tx_data_len);
}
+
+class StackL2capChannelTest : public StackL2capTest {
+ protected:
+ void SetUp() override { StackL2capTest::SetUp(); }
+
+ void TearDown() override { StackL2capTest::TearDown(); }
+
+ tL2C_CCB ccb_ = {
+ .in_use = true,
+ .chnl_state = CST_OPEN, // tL2C_CHNL_STATE
+ .local_conn_cfg =
+ {
+ // tL2CAP_LE_CFG_INFO
+ .result = 0,
+ .mtu = 100,
+ .mps = 100,
+ .credits = L2CA_LeCreditDefault(),
+ .number_of_channels = L2CAP_CREDIT_BASED_MAX_CIDS,
+ },
+ .peer_conn_cfg =
+ {
+ // tL2CAP_LE_CFG_INFO
+ .result = 0,
+ .mtu = 100,
+ .mps = 100,
+ .credits = L2CA_LeCreditDefault(),
+ .number_of_channels = L2CAP_CREDIT_BASED_MAX_CIDS,
+ },
+ .is_first_seg = false,
+ .ble_sdu = nullptr, // BT_HDR*; Buffer for storing unassembled sdu
+ .ble_sdu_length = 0, /* Length of unassembled sdu length*/
+ .p_next_ccb = nullptr, // struct t_l2c_ccb* Next CCB in the chain
+ .p_prev_ccb = nullptr, // struct t_l2c_ccb* Previous CCB in the chain
+ .p_lcb = nullptr, // struct t_l2c_linkcb* Link this CCB is assigned to
+ .local_cid = 40,
+ .remote_cid = 80,
+ .l2c_ccb_timer = nullptr, // alarm_t* CCB Timer Entry
+ .p_rcb = nullptr, // tL2C_RCB* Registration CB for this Channel
+ .config_done = 0, // Configuration flag word
+ .remote_config_rsp_result = 0, // The config rsp result from remote
+ .local_id = 12, // Transaction ID for local trans
+ .remote_id = 22, // Transaction ID for local
+ .flags = 0,
+ .connection_initiator = false,
+ .our_cfg = {}, // tL2CAP_CFG_INFO Our saved configuration options
+ .peer_cfg = {}, // tL2CAP_CFG_INFO Peer's saved configuration options
+ .xmit_hold_q = nullptr, // fixed_queue_t* Transmit data hold queue
+ .cong_sent = false,
+ .buff_quota = 0,
+
+ .ccb_priority =
+ L2CAP_CHNL_PRIORITY_HIGH, // tL2CAP_CHNL_PRIORITY Channel priority
+ .tx_data_rate = 0, // tL2CAP_CHNL_PRIORITY Channel Tx data rate
+ .rx_data_rate = 0, // tL2CAP_CHNL_PRIORITY Channel Rx data rate
+
+ .ertm_info =
+ {
+ // .tL2CAP_ERTM_INFO
+ .preferred_mode = 0,
+ },
+ .fcrb =
+ {
+ // tL2C_FCRB
+ .next_tx_seq = 0,
+ .last_rx_ack = 0,
+ .next_seq_expected = 0,
+ .last_ack_sent = 0,
+ .num_tries = 0,
+ .max_held_acks = 0,
+ .remote_busy = false,
+ .rej_sent = false,
+ .srej_sent = false,
+ .wait_ack = false,
+ .rej_after_srej = false,
+ .send_f_rsp = false,
+ .rx_sdu_len = 0,
+ .p_rx_sdu =
+ nullptr, // BT_HDR* Buffer holding the SDU being received
+ .waiting_for_ack_q = nullptr, // fixed_queue_t*
+ .srej_rcv_hold_q = nullptr, // fixed_queue_t*
+ .retrans_q = nullptr, // fixed_queue_t*
+ .ack_timer = nullptr, // alarm_t*
+ .mon_retrans_timer = nullptr, // alarm_t*
+ },
+ .tx_mps = 0,
+ .max_rx_mtu = 0,
+ .fcr_cfg_tries = 0,
+ .peer_cfg_already_rejected = false,
+ .out_cfg_fcr_present = false,
+ .is_flushable = false,
+ .fixed_chnl_idle_tout = 0,
+ .tx_data_len = 0,
+ .remote_credit_count = 0,
+ .ecoc = false,
+ .reconfig_started = false,
+ .metrics = {},
+ };
+};
+
+TEST_F(StackL2capChannelTest, l2c_lcc_proc_pdu__FirstSegment) {
+ ccb_.is_first_seg = true;
+
+ BT_HDR* p_buf = (BT_HDR*)osi_calloc(sizeof(BT_HDR) + 32);
+ p_buf->len = 32;
+
+ l2c_lcc_proc_pdu(&ccb_, p_buf);
+}
+
+TEST_F(StackL2capChannelTest, l2c_lcc_proc_pdu__NextSegment) {
+ BT_HDR* p_buf = (BT_HDR*)osi_calloc(sizeof(BT_HDR) + 32);
+ p_buf->len = 32;
+
+ l2c_lcc_proc_pdu(&ccb_, p_buf);
+}
diff --git a/system/test/mock/mock_bta_gatts_api.cc b/system/test/mock/mock_bta_gatts_api.cc
index 68380e4..ba86ae5 100644
--- a/system/test/mock/mock_bta_gatts_api.cc
+++ b/system/test/mock/mock_bta_gatts_api.cc
@@ -85,3 +85,4 @@
BTA_GATTS_AddServiceCb cb) {
mock_function_count_map[__func__]++;
}
+void BTA_GATTS_InitBonded(void) { mock_function_count_map[__func__]++; }
diff --git a/system/test/mock/mock_bta_hearing_aid.cc b/system/test/mock/mock_bta_hearing_aid.cc
index eabcd09..200df2d 100644
--- a/system/test/mock/mock_bta_hearing_aid.cc
+++ b/system/test/mock/mock_bta_hearing_aid.cc
@@ -52,22 +52,39 @@
mock_function_count_map[__func__]++;
return 0;
}
+
void HearingAid::AddFromStorage(const HearingDevice& dev_info,
uint16_t is_acceptlisted) {
mock_function_count_map[__func__]++;
}
+
void HearingAid::DebugDump(int fd) { mock_function_count_map[__func__]++; }
-HearingAid* HearingAid::Get() {
- mock_function_count_map[__func__]++;
- return nullptr;
-}
+
bool HearingAid::IsHearingAidRunning() {
mock_function_count_map[__func__]++;
return false;
}
+
void HearingAid::CleanUp() { mock_function_count_map[__func__]++; }
+
void HearingAid::Initialize(
bluetooth::hearing_aid::HearingAidCallbacks* callbacks,
base::Closure initCb) {
mock_function_count_map[__func__]++;
}
+
+void HearingAid::Connect(const RawAddress& address) {
+ mock_function_count_map[__func__]++;
+}
+
+void HearingAid::Disconnect(const RawAddress& address) {
+ mock_function_count_map[__func__]++;
+}
+
+void HearingAid::AddToAcceptlist(const RawAddress& address) {
+ mock_function_count_map[__func__]++;
+}
+
+void HearingAid::SetVolume(int8_t volume) {
+ mock_function_count_map[__func__]++;
+}
diff --git a/system/test/mock/mock_bta_hf_client_api.cc b/system/test/mock/mock_bta_hf_client_api.cc
index 19f6913..f5be68a 100644
--- a/system/test/mock/mock_bta_hf_client_api.cc
+++ b/system/test/mock/mock_bta_hf_client_api.cc
@@ -51,10 +51,15 @@
void BTA_HfClientClose(uint16_t handle) { mock_function_count_map[__func__]++; }
void BTA_HfClientDisable(void) { mock_function_count_map[__func__]++; }
void BTA_HfClientDumpStatistics(int fd) { mock_function_count_map[__func__]++; }
-void BTA_HfClientOpen(const RawAddress& bd_addr, uint16_t* p_handle) {
+bt_status_t BTA_HfClientOpen(const RawAddress& bd_addr, uint16_t* p_handle) {
mock_function_count_map[__func__]++;
+ return BT_STATUS_SUCCESS;
}
void BTA_HfClientSendAT(uint16_t handle, tBTA_HF_CLIENT_AT_CMD_TYPE at,
uint32_t val1, uint32_t val2, const char* str) {
mock_function_count_map[__func__]++;
}
+int get_default_hf_client_features() {
+ mock_function_count_map[__func__]++;
+ return 0;
+}
diff --git a/system/test/mock/mock_bta_hfp_api.cc b/system/test/mock/mock_bta_hfp_api.cc
new file mode 100644
index 0000000..b9dc9dd
--- /dev/null
+++ b/system/test/mock/mock_bta_hfp_api.cc
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "bta/include/bta_hfp_api.h"
+
+#define DEFAULT_BTA_HFP_VERSION HFP_VERSION_1_7
+
+int get_default_hfp_version() { return DEFAULT_BTA_HFP_VERSION; }
diff --git a/system/test/mock/mock_main_shim.cc b/system/test/mock/mock_main_shim.cc
index 69cd8df..1e997ce 100644
--- a/system/test/mock/mock_main_shim.cc
+++ b/system/test/mock/mock_main_shim.cc
@@ -25,6 +25,7 @@
extern std::map<std::string, int> mock_function_count_map;
#define LOG_TAG "bt_shim"
+
#include "gd/common/init_flags.h"
#include "main/shim/entry.h"
#include "main/shim/shim.h"
@@ -45,9 +46,14 @@
mock_function_count_map[__func__]++;
return false;
}
+namespace test {
+namespace mock {
+bool bluetooth_shim_is_gd_stack_started_up = false;
+}
+} // namespace test
bool bluetooth::shim::is_gd_stack_started_up() {
mock_function_count_map[__func__]++;
- return false;
+ return test::mock::bluetooth_shim_is_gd_stack_started_up;
}
bool bluetooth::shim::is_gd_link_policy_enabled() {
mock_function_count_map[__func__]++;
diff --git a/system/test/mock/mock_main_shim_metrics_api.cc b/system/test/mock/mock_main_shim_metrics_api.cc
index 30aeeb1..8330b85 100644
--- a/system/test/mock/mock_main_shim_metrics_api.cc
+++ b/system/test/mock/mock_main_shim_metrics_api.cc
@@ -128,8 +128,8 @@
raw_address, handle, cmd_status, transmit_power_level);
}
void bluetooth::shim::LogMetricSmpPairingEvent(
- const RawAddress& raw_address, uint8_t smp_cmd,
- android::bluetooth::DirectionEnum direction, uint8_t smp_fail_reason) {
+ const RawAddress& raw_address, uint16_t smp_cmd,
+ android::bluetooth::DirectionEnum direction, uint16_t smp_fail_reason) {
mock_function_count_map[__func__]++;
test::mock::main_shim_metrics_api::LogMetricSmpPairingEvent(
raw_address, smp_cmd, direction, smp_fail_reason);
diff --git a/system/test/mock/mock_main_shim_metrics_api.h b/system/test/mock/mock_main_shim_metrics_api.h
index b42529f..d85088f 100644
--- a/system/test/mock/mock_main_shim_metrics_api.h
+++ b/system/test/mock/mock_main_shim_metrics_api.h
@@ -171,19 +171,19 @@
};
extern struct LogMetricReadTxPowerLevelResult LogMetricReadTxPowerLevelResult;
// Name: LogMetricSmpPairingEvent
-// Params: const RawAddress& raw_address, uint8_t smp_cmd,
+// Params: const RawAddress& raw_address, uint16_t smp_cmd,
// android::bluetooth::DirectionEnum direction, uint8_t smp_fail_reason Returns:
// void
struct LogMetricSmpPairingEvent {
- std::function<void(const RawAddress& raw_address, uint8_t smp_cmd,
+ std::function<void(const RawAddress& raw_address, uint16_t smp_cmd,
android::bluetooth::DirectionEnum direction,
- uint8_t smp_fail_reason)>
- body{[](const RawAddress& raw_address, uint8_t smp_cmd,
+ uint16_t smp_fail_reason)>
+ body{[](const RawAddress& raw_address, uint16_t smp_cmd,
android::bluetooth::DirectionEnum direction,
- uint8_t smp_fail_reason) {}};
- void operator()(const RawAddress& raw_address, uint8_t smp_cmd,
+ uint16_t smp_fail_reason) {}};
+ void operator()(const RawAddress& raw_address, uint16_t smp_cmd,
android::bluetooth::DirectionEnum direction,
- uint8_t smp_fail_reason) {
+ uint16_t smp_fail_reason) {
body(raw_address, smp_cmd, direction, smp_fail_reason);
};
};
diff --git a/system/test/mock/mock_stack_acl.cc b/system/test/mock/mock_stack_acl.cc
index 91ee709..771e256 100644
--- a/system/test/mock/mock_stack_acl.cc
+++ b/system/test/mock/mock_stack_acl.cc
@@ -621,9 +621,9 @@
mock_function_count_map[__func__]++;
test::mock::stack_acl::btm_read_failed_contact_counter_timeout(data);
}
-void btm_read_link_quality_complete(uint8_t* p) {
+void btm_read_link_quality_complete(uint8_t* p, uint16_t evt_len) {
mock_function_count_map[__func__]++;
- test::mock::stack_acl::btm_read_link_quality_complete(p);
+ test::mock::stack_acl::btm_read_link_quality_complete(p, evt_len);
}
void btm_read_link_quality_timeout(UNUSED_ATTR void* data) {
mock_function_count_map[__func__]++;
@@ -660,17 +660,17 @@
test::mock::stack_acl::btm_read_remote_version_complete(
status, handle, lmp_version, manufacturer, lmp_subversion);
}
-void btm_read_rssi_complete(uint8_t* p) {
+void btm_read_rssi_complete(uint8_t* p, uint16_t evt_len) {
mock_function_count_map[__func__]++;
- test::mock::stack_acl::btm_read_rssi_complete(p);
+ test::mock::stack_acl::btm_read_rssi_complete(p, evt_len);
}
void btm_read_rssi_timeout(UNUSED_ATTR void* data) {
mock_function_count_map[__func__]++;
test::mock::stack_acl::btm_read_rssi_timeout(data);
}
-void btm_read_tx_power_complete(uint8_t* p, bool is_ble) {
+void btm_read_tx_power_complete(uint8_t* p, uint16_t evt_len, bool is_ble) {
mock_function_count_map[__func__]++;
- test::mock::stack_acl::btm_read_tx_power_complete(p, is_ble);
+ test::mock::stack_acl::btm_read_tx_power_complete(p, evt_len, is_ble);
}
void btm_read_tx_power_timeout(UNUSED_ATTR void* data) {
mock_function_count_map[__func__]++;
diff --git a/system/test/mock/mock_stack_acl.h b/system/test/mock/mock_stack_acl.h
index 8747ed7..d2efb0c 100644
--- a/system/test/mock/mock_stack_acl.h
+++ b/system/test/mock/mock_stack_acl.h
@@ -1091,8 +1091,8 @@
// Params: uint8_t* p
// Returns: void
struct btm_read_link_quality_complete {
- std::function<void(uint8_t* p)> body{[](uint8_t* p) { ; }};
- void operator()(uint8_t* p) { body(p); };
+ std::function<void(uint8_t* p, uint16_t evt_len)> body{[](uint8_t* p, uint16_t evt_len) { ; }};
+ void operator()(uint8_t* p, uint16_t evt_len) { body(p, evt_len); };
};
extern struct btm_read_link_quality_complete btm_read_link_quality_complete;
// Name: btm_read_link_quality_timeout
@@ -1180,8 +1180,9 @@
// Params: uint8_t* p
// Returns: void
struct btm_read_rssi_complete {
- std::function<void(uint8_t* p)> body{[](uint8_t* p) { ; }};
- void operator()(uint8_t* p) { body(p); };
+ std::function<void(uint8_t* p, uint16_t evt_len)> body{
+ [](uint8_t* pm, uint16_t evt_len) { ; }};
+ void operator()(uint8_t* p, uint16_t evt_len) { body(p, evt_len); };
};
extern struct btm_read_rssi_complete btm_read_rssi_complete;
// Name: btm_read_rssi_timeout
@@ -1197,9 +1198,11 @@
// Params: uint8_t* p, bool is_ble
// Returns: void
struct btm_read_tx_power_complete {
- std::function<void(uint8_t* p, bool is_ble)> body{
- [](uint8_t* p, bool is_ble) { ; }};
- void operator()(uint8_t* p, bool is_ble) { body(p, is_ble); };
+ std::function<void(uint8_t* p, uint16_t evt_len, bool is_ble)> body{
+ [](uint8_t* p, uint16_t evt_len, bool is_ble) { ; }};
+ void operator()(uint8_t* p, uint16_t evt_len, bool is_ble) {
+ body(p, evt_len, is_ble);
+ };
};
extern struct btm_read_tx_power_complete btm_read_tx_power_complete;
// Name: btm_read_tx_power_timeout
diff --git a/system/test/mock/mock_stack_avrc_api.cc b/system/test/mock/mock_stack_avrc_api.cc
index c9e58a9..235613b 100644
--- a/system/test/mock/mock_stack_avrc_api.cc
+++ b/system/test/mock/mock_stack_avrc_api.cc
@@ -40,6 +40,10 @@
#define UNUSED_ATTR
#endif
+bool avrcp_absolute_volume_is_enabled() {
+ mock_function_count_map[__func__]++;
+ return true;
+}
uint16_t AVRC_Close(uint8_t handle) {
mock_function_count_map[__func__]++;
return 0;
diff --git a/system/test/mock/mock_stack_btm_ble.cc b/system/test/mock/mock_stack_btm_ble.cc
index 5ebd1a5..ee5ac6f 100644
--- a/system/test/mock/mock_stack_btm_ble.cc
+++ b/system/test/mock/mock_stack_btm_ble.cc
@@ -226,7 +226,7 @@
const Octet16& stk) {
mock_function_count_map[__func__]++;
}
-void btm_ble_rand_enc_complete(uint8_t* p, uint16_t op_code,
+void btm_ble_rand_enc_complete(uint8_t* p, uint16_t evt_len, uint16_t op_code,
tBTM_RAND_ENC_CB* p_enc_cplt_cback) {
mock_function_count_map[__func__]++;
}
diff --git a/system/test/mock/mock_stack_btm_ble_gap.cc b/system/test/mock/mock_stack_btm_ble_gap.cc
index cf3294c..aadc5f2 100644
--- a/system/test/mock/mock_stack_btm_ble_gap.cc
+++ b/system/test/mock/mock_stack_btm_ble_gap.cc
@@ -190,7 +190,7 @@
void btm_ble_process_phy_update_pkt(uint8_t len, uint8_t* data) {
mock_function_count_map[__func__]++;
}
-void btm_ble_read_remote_features_complete(uint8_t* p) {
+void btm_ble_read_remote_features_complete(uint8_t* p, uint8_t length) {
mock_function_count_map[__func__]++;
}
void btm_ble_read_remote_name_cmpl(bool status, const RawAddress& bda,
@@ -220,7 +220,7 @@
tHCI_STATUS status) {
mock_function_count_map[__func__]++;
}
-void btm_ble_write_adv_enable_complete(uint8_t* p) {
+void btm_ble_write_adv_enable_complete(uint8_t* p, uint16_t evt_len) {
mock_function_count_map[__func__]++;
}
void btm_clear_all_pending_le_entry(void) {
diff --git a/system/test/mock/mock_stack_btm_dev.cc b/system/test/mock/mock_stack_btm_dev.cc
index a01217b..cc959d3 100644
--- a/system/test/mock/mock_stack_btm_dev.cc
+++ b/system/test/mock/mock_stack_btm_dev.cc
@@ -109,3 +109,16 @@
void wipe_secrets_and_remove(tBTM_SEC_DEV_REC* p_dev_rec) {
mock_function_count_map[__func__]++;
}
+void btm_dev_consolidate_existing_connections(const RawAddress& bd_addr) {
+ mock_function_count_map[__func__]++;
+}
+void BTM_SecDump(const std::string& label) {
+ mock_function_count_map[__func__]++;
+}
+void BTM_SecDumpDev(const RawAddress& bd_addr) {
+ mock_function_count_map[__func__]++;
+}
+std::vector<tBTM_SEC_DEV_REC*> btm_get_sec_dev_rec() {
+ mock_function_count_map[__func__]++;
+ return {};
+}
diff --git a/system/test/mock/mock_stack_btm_devctl.cc b/system/test/mock/mock_stack_btm_devctl.cc
index a51098f..1b9db1a 100644
--- a/system/test/mock/mock_stack_btm_devctl.cc
+++ b/system/test/mock/mock_stack_btm_devctl.cc
@@ -105,7 +105,8 @@
}
void BTM_db_reset(void) { mock_function_count_map[__func__]++; }
void BTM_reset_complete() { mock_function_count_map[__func__]++; }
-void btm_delete_stored_link_key_complete(uint8_t* p) {
+void btm_delete_stored_link_key_complete(uint8_t* p,
+ UNUSED_ATTR uint16_t evt_len) {
mock_function_count_map[__func__]++;
}
void btm_dev_free() { mock_function_count_map[__func__]++; }
diff --git a/system/test/mock/mock_stack_btm_sec.cc b/system/test/mock/mock_stack_btm_sec.cc
index 56a0db1..ea6ce17 100644
--- a/system/test/mock/mock_stack_btm_sec.cc
+++ b/system/test/mock/mock_stack_btm_sec.cc
@@ -239,7 +239,7 @@
void NotifyBondingCanceled(tBTM_STATUS btm_status) {
mock_function_count_map[__func__]++;
}
-void btm_create_conn_cancel_complete(const uint8_t* p) {
+void btm_create_conn_cancel_complete(const uint8_t* p, uint16_t evt_len) {
mock_function_count_map[__func__]++;
}
void btm_io_capabilities_req(const RawAddress& p) {
@@ -251,7 +251,7 @@
void btm_proc_sp_req_evt(tBTM_SP_EVT event, const uint8_t* p) {
mock_function_count_map[__func__]++;
}
-void btm_read_local_oob_complete(uint8_t* p) {
+void btm_read_local_oob_complete(uint8_t* p, uint16_t evt_len) {
mock_function_count_map[__func__]++;
}
void btm_rem_oob_req(const uint8_t* p) { mock_function_count_map[__func__]++; }
diff --git a/system/test/mock/mock_stack_gatt_connection_manager.cc b/system/test/mock/mock_stack_gatt_connection_manager.cc
index 99f2904..372b4f0 100644
--- a/system/test/mock/mock_stack_gatt_connection_manager.cc
+++ b/system/test/mock/mock_stack_gatt_connection_manager.cc
@@ -92,3 +92,8 @@
void connection_manager::reset(bool after_reset) {
mock_function_count_map[__func__]++;
}
+
+bool connection_manager::is_background_connection(const RawAddress& address) {
+ mock_function_count_map[__func__]++;
+ return false;
+}
diff --git a/system/test/mock/mock_stack_l2cap_api.cc b/system/test/mock/mock_stack_l2cap_api.cc
index 27cd1e3..7311b3b 100644
--- a/system/test/mock/mock_stack_l2cap_api.cc
+++ b/system/test/mock/mock_stack_l2cap_api.cc
@@ -99,6 +99,8 @@
struct L2CA_SetChnlFlushability L2CA_SetChnlFlushability;
struct L2CA_FlushChannel L2CA_FlushChannel;
struct L2CA_IsLinkEstablished L2CA_IsLinkEstablished;
+struct L2CA_LeCreditDefault L2CA_LeCreditDefault;
+struct L2CA_LeCreditThreshold L2CA_LeCreditThreshold;
} // namespace stack_l2cap_api
} // namespace mock
@@ -287,5 +289,13 @@
return test::mock::stack_l2cap_api::L2CA_IsLinkEstablished(bd_addr,
transport);
}
+uint16_t L2CA_LeCreditDefault() {
+ mock_function_count_map[__func__]++;
+ return test::mock::stack_l2cap_api::L2CA_LeCreditDefault();
+}
+uint16_t L2CA_LeCreditThreshold() {
+ mock_function_count_map[__func__]++;
+ return test::mock::stack_l2cap_api::L2CA_LeCreditThreshold();
+}
// END mockcify generation
diff --git a/system/test/mock/mock_stack_l2cap_api.h b/system/test/mock/mock_stack_l2cap_api.h
index 4488101..592485c 100644
--- a/system/test/mock/mock_stack_l2cap_api.h
+++ b/system/test/mock/mock_stack_l2cap_api.h
@@ -485,6 +485,22 @@
};
};
extern struct L2CA_IsLinkEstablished L2CA_IsLinkEstablished;
+// Name: L2CA_LeCreditDefault
+// Params:
+// Returns: uint16_t
+struct L2CA_LeCreditDefault {
+ std::function<uint16_t()> body{[]() { return 0; }};
+ uint16_t operator()() { return body(); };
+};
+extern struct L2CA_LeCreditDefault L2CA_LeCreditDefault;
+// Name: L2CA_LeCreditThreshold
+// Params:
+// Returns: uint16_t
+struct L2CA_LeCreditThreshold {
+ std::function<uint16_t()> body{[]() { return 0; }};
+ uint16_t operator()() { return body(); };
+};
+extern struct L2CA_LeCreditThreshold L2CA_LeCreditThreshold;
} // namespace stack_l2cap_api
} // namespace mock
diff --git a/system/test/mock/mock_stack_metrics_logging.cc b/system/test/mock/mock_stack_metrics_logging.cc
index 1f51191..028636d 100644
--- a/system/test/mock/mock_stack_metrics_logging.cc
+++ b/system/test/mock/mock_stack_metrics_logging.cc
@@ -86,9 +86,9 @@
address, connection_handle, direction, link_type, hci_cmd, hci_event,
hci_ble_event, cmd_status, reason_code);
}
-void log_smp_pairing_event(const RawAddress& address, uint8_t smp_cmd,
+void log_smp_pairing_event(const RawAddress& address, uint16_t smp_cmd,
android::bluetooth::DirectionEnum direction,
- uint8_t smp_fail_reason) {
+ uint16_t smp_fail_reason) {
mock_function_count_map[__func__]++;
test::mock::stack_metrics_logging::log_smp_pairing_event(
address, smp_cmd, direction, smp_fail_reason);
diff --git a/system/test/mock/mock_stack_metrics_logging.h b/system/test/mock/mock_stack_metrics_logging.h
index 967b2f8..8b695f4 100644
--- a/system/test/mock/mock_stack_metrics_logging.h
+++ b/system/test/mock/mock_stack_metrics_logging.h
@@ -96,19 +96,19 @@
};
extern struct log_link_layer_connection_event log_link_layer_connection_event;
// Name: log_smp_pairing_event
-// Params: const RawAddress& address, uint8_t smp_cmd,
+// Params: const RawAddress& address, uint16_t smp_cmd,
// android::bluetooth::DirectionEnum direction, uint8_t smp_fail_reason Returns:
// void
struct log_smp_pairing_event {
- std::function<void(const RawAddress& address, uint8_t smp_cmd,
+ std::function<void(const RawAddress& address, uint16_t smp_cmd,
android::bluetooth::DirectionEnum direction,
- uint8_t smp_fail_reason)>
- body{[](const RawAddress& address, uint8_t smp_cmd,
+ uint16_t smp_fail_reason)>
+ body{[](const RawAddress& address, uint16_t smp_cmd,
android::bluetooth::DirectionEnum direction,
- uint8_t smp_fail_reason) {}};
- void operator()(const RawAddress& address, uint8_t smp_cmd,
+ uint16_t smp_fail_reason) {}};
+ void operator()(const RawAddress& address, uint16_t smp_cmd,
android::bluetooth::DirectionEnum direction,
- uint8_t smp_fail_reason) {
+ uint16_t smp_fail_reason) {
body(address, smp_cmd, direction, smp_fail_reason);
};
};
diff --git a/system/test/mock/mock_stack_smp_api.cc b/system/test/mock/mock_stack_smp_api.cc
index b3b175c..59f13f9 100644
--- a/system/test/mock/mock_stack_smp_api.cc
+++ b/system/test/mock/mock_stack_smp_api.cc
@@ -84,6 +84,9 @@
mock_function_count_map[__func__]++;
}
-void SMP_CrLocScOobData() { mock_function_count_map[__func__]++; }
+bool SMP_CrLocScOobData() {
+ mock_function_count_map[__func__]++;
+ return false;
+}
void SMP_ClearLocScOobData() { mock_function_count_map[__func__]++; }
diff --git a/system/test/stub/osi.cc b/system/test/stub/osi.cc
index 6c87947..7041855 100644
--- a/system/test/stub/osi.cc
+++ b/system/test/stub/osi.cc
@@ -381,7 +381,7 @@
alarm_t* alarm_new(const char* name) {
mock_function_count_map[__func__]++;
- return nullptr;
+ return (alarm_t*)new uint8_t[30];
}
alarm_t* alarm_new_periodic(const char* name) {
mock_function_count_map[__func__]++;
@@ -404,7 +404,11 @@
}
void alarm_cleanup(void) { mock_function_count_map[__func__]++; }
void alarm_debug_dump(int fd) { mock_function_count_map[__func__]++; }
-void alarm_free(alarm_t* alarm) { mock_function_count_map[__func__]++; }
+void alarm_free(alarm_t* alarm) {
+ uint8_t* ptr = (uint8_t*)alarm;
+ delete[] ptr;
+ mock_function_count_map[__func__]++;
+}
void alarm_set(alarm_t* alarm, uint64_t interval_ms, alarm_callback_t cb,
void* data) {
mock_function_count_map[__func__]++;
diff --git a/system/test/suite/Android.bp b/system/test/suite/Android.bp
index 30f6f20..85bcb22 100644
--- a/system/test/suite/Android.bp
+++ b/system/test/suite/Android.bp
@@ -85,6 +85,7 @@
"libbt-sbc-encoder",
"libbt-stack",
"libbt-utils",
+ "libcom.android.sysprop.bluetooth",
"libflatbuffers-cpp",
"libFraunhoferAAC",
"libg722codec",
diff --git a/system/types/ble_address_with_type.h b/system/types/ble_address_with_type.h
index 38a52cc..250072d 100644
--- a/system/types/ble_address_with_type.h
+++ b/system/types/ble_address_with_type.h
@@ -116,9 +116,19 @@
return (other & ~kBleAddressIdentityBit) ==
(type & ~kBleAddressIdentityBit);
}
+
std::string ToString() const {
return std::string(bda.ToString() + "[" + AddressTypeText(type) + "]");
}
+
+ std::string ToStringForLogging() const {
+ return bda.ToStringForLogging() + "[" + AddressTypeText(type) + "]";
+ }
+
+ std::string ToRedactedStringForLogging() const {
+ return bda.ToRedactedStringForLogging() + "[" + AddressTypeText(type) + "]";
+ }
+
bool operator==(const tBLE_BD_ADDR rhs) const {
return rhs.type == type && rhs.bda == bda;
}
diff --git a/system/types/raw_address.cc b/system/types/raw_address.cc
index afaf4ef..0cd9de0 100644
--- a/system/types/raw_address.cc
+++ b/system/types/raw_address.cc
@@ -38,12 +38,22 @@
std::copy(mac.begin(), mac.end(), address);
}
-std::string RawAddress::ToString() const {
+std::string RawAddress::ToString() const { return ToColonSepHexString(); }
+
+std::string RawAddress::ToColonSepHexString() const {
return base::StringPrintf("%02x:%02x:%02x:%02x:%02x:%02x", address[0],
address[1], address[2], address[3], address[4],
address[5]);
}
+std::string RawAddress::ToStringForLogging() const {
+ return ToColonSepHexString();
+}
+
+std::string RawAddress::ToRedactedStringForLogging() const {
+ return base::StringPrintf("xx:xx:xx:xx:%02x:%02x", address[4], address[5]);
+}
+
std::array<uint8_t, RawAddress::kLength> RawAddress::ToArray() const {
std::array<uint8_t, kLength> mac;
std::copy(std::begin(address), std::end(address), std::begin(mac));
diff --git a/system/types/raw_address.h b/system/types/raw_address.h
index b3e7530..d623b9f 100644
--- a/system/types/raw_address.h
+++ b/system/types/raw_address.h
@@ -46,8 +46,22 @@
bool IsEmpty() const { return *this == kEmpty; }
+ // TODO (b/258090765): remove it and
+ // replace its usage with ToColonSepHexString
std::string ToString() const;
+ // Return a string representation in the form of
+ // hexadecimal string separated by colon (:), e.g.,
+ // "12:34:56:ab:cd:ef"
+ std::string ToColonSepHexString() const;
+ // same as ToColonSepHexString
+ std::string ToStringForLogging() const;
+
+ // Similar with ToColonHexString, ToRedactedStringForLogging returns a
+ // colon separated hexadecimal reprentation of the address but, with the
+ // leftmost 4 bytes masked with "xx", e.g., "xx:xx:xx:xx:ab:cd".
+ std::string ToRedactedStringForLogging() const;
+
// Converts |string| to RawAddress and places it in |to|. If |from| does
// not represent a Bluetooth address, |to| is not modified and this function
// returns false. Otherwise, it returns true.
diff --git a/system/types/test/raw_address_unittest.cc b/system/types/test/raw_address_unittest.cc
index 3a34295..513c8b7 100644
--- a/system/types/test/raw_address_unittest.cc
+++ b/system/types/test/raw_address_unittest.cc
@@ -198,3 +198,14 @@
std::array<uint8_t, 6> mac2 = bdaddr.ToArray();
ASSERT_EQ(mac, mac2);
}
+
+TEST(RawAddress, ToStringForLoggingTest) {
+ std::array<uint8_t, 6> addr_bytes = {0x11, 0x22, 0x33, 0x44, 0x55, 0xab};
+ RawAddress addr(addr_bytes);
+ const std::string redacted_loggable_str = "xx:xx:xx:xx:55:ab";
+ const std::string loggbable_str = "11:22:33:44:55:ab";
+ std::string ret1 = addr.ToStringForLogging();
+ ASSERT_STREQ(ret1.c_str(), loggbable_str.c_str());
+ std::string ret2 = addr.ToRedactedStringForLogging();
+ ASSERT_STREQ(ret2.c_str(), redacted_loggable_str.c_str());
+}
diff --git a/tools/rootcanal/model/hci/h4_data_channel_packetizer.cc b/tools/rootcanal/model/hci/h4_data_channel_packetizer.cc
index ad103ba..3f73949 100644
--- a/tools/rootcanal/model/hci/h4_data_channel_packetizer.cc
+++ b/tools/rootcanal/model/hci/h4_data_channel_packetizer.cc
@@ -39,7 +39,7 @@
PacketReadCallback sco_cb, PacketReadCallback iso_cb,
ClientDisconnectCallback disconnect_cb)
: uart_socket_(socket),
- h4_parser_(command_cb, event_cb, acl_cb, sco_cb, iso_cb),
+ h4_parser_(command_cb, event_cb, acl_cb, sco_cb, iso_cb, true),
disconnect_cb_(std::move(disconnect_cb)) {}
size_t H4DataChannelPacketizer::Send(uint8_t type, const uint8_t* data,
diff --git a/tools/rootcanal/model/hci/h4_parser.cc b/tools/rootcanal/model/hci/h4_parser.cc
index 1471f1e..0498c99 100644
--- a/tools/rootcanal/model/hci/h4_parser.cc
+++ b/tools/rootcanal/model/hci/h4_parser.cc
@@ -16,8 +16,8 @@
#include "model/hci/h4_parser.h" // for H4Parser, PacketType, H4Pars...
-#include <stddef.h> // for size_t
-
+#include <array>
+#include <cstddef> // for size_t
#include <cstdint> // for uint8_t, int32_t
#include <functional> // for function
#include <utility> // for move
@@ -60,12 +60,13 @@
H4Parser::H4Parser(PacketReadCallback command_cb, PacketReadCallback event_cb,
PacketReadCallback acl_cb, PacketReadCallback sco_cb,
- PacketReadCallback iso_cb)
+ PacketReadCallback iso_cb, bool enable_recovery_state)
: command_cb_(std::move(command_cb)),
event_cb_(std::move(event_cb)),
acl_cb_(std::move(acl_cb)),
sco_cb_(std::move(sco_cb)),
- iso_cb_(std::move(iso_cb)) {}
+ iso_cb_(std::move(iso_cb)),
+ enable_recovery_state_(enable_recovery_state) {}
void H4Parser::OnPacketReady() {
switch (hci_packet_type_) {
@@ -95,6 +96,7 @@
size_t H4Parser::BytesRequested() {
switch (state_) {
case HCI_TYPE:
+ case HCI_RECOVERY:
return 1;
case HCI_PREAMBLE:
case HCI_PAYLOAD:
@@ -102,7 +104,7 @@
}
}
-bool H4Parser::Consume(uint8_t* buffer, int32_t bytes_read) {
+bool H4Parser::Consume(const uint8_t* buffer, int32_t bytes_read) {
size_t bytes_to_read = BytesRequested();
if (bytes_read <= 0) {
LOG_INFO("remote disconnected, or unhandled error?");
@@ -128,6 +130,36 @@
packet_type_ = *buffer;
packet_.clear();
break;
+
+ case HCI_RECOVERY: {
+ // Skip all received bytes until the HCI Reset command is received.
+ // The parser can end up in a bad state when the host is restarted.
+ const std::array<uint8_t, 4> reset_command{0x01, 0x03, 0x0c, 0x00};
+ size_t offset = packet_.size();
+ LOG_WARN("Received byte in recovery state : 0x%x",
+ static_cast<unsigned>(*buffer));
+ packet_.push_back(*buffer);
+
+ // Last byte does not match expected byte in the sequence.
+ // Drop all the bytes and start over.
+ if (packet_[offset] != reset_command[offset]) {
+ packet_.clear();
+ // The mismatched byte can also be the first of the correct sequence.
+ if (*buffer == reset_command[0]) {
+ packet_.push_back(*buffer);
+ }
+ }
+
+ // Received full reset command.
+ if (packet_.size() == reset_command.size()) {
+ LOG_INFO("Received HCI Reset command, exiting recovery state");
+ // Pop the Idc from the received packet.
+ packet_.erase(packet_.begin());
+ bytes_wanted_ = 0;
+ }
+ break;
+ }
+
case HCI_PREAMBLE:
case HCI_PAYLOAD:
packet_.insert(packet_.end(), buffer, buffer + bytes_read);
@@ -143,13 +175,21 @@
hci_packet_type_ != PacketType::COMMAND &&
hci_packet_type_ != PacketType::EVENT &&
hci_packet_type_ != PacketType::ISO) {
- LOG_ALWAYS_FATAL("Unimplemented packet type %hhd", packet_type_);
+ if (!enable_recovery_state_) {
+ LOG_ALWAYS_FATAL("Received invalid packet type 0x%x",
+ static_cast<unsigned>(packet_type_));
+ }
+ LOG_ERROR("Received invalid packet type 0x%x, entering recovery state",
+ static_cast<unsigned>(packet_type_));
+ state_ = HCI_RECOVERY;
+ hci_packet_type_ = PacketType::COMMAND;
+ bytes_wanted_ = 1;
+ } else {
+ state_ = HCI_PREAMBLE;
+ bytes_wanted_ = preamble_size[static_cast<size_t>(hci_packet_type_)];
}
- state_ = HCI_PREAMBLE;
- bytes_wanted_ = preamble_size[static_cast<size_t>(hci_packet_type_)];
break;
case HCI_PREAMBLE:
-
if (bytes_wanted_ == 0) {
size_t payload_size =
HciGetPacketLengthForType(hci_packet_type_, packet_.data());
@@ -162,6 +202,7 @@
}
}
break;
+ case HCI_RECOVERY:
case HCI_PAYLOAD:
if (bytes_wanted_ == 0) {
OnPacketReady();
diff --git a/tools/rootcanal/model/hci/h4_parser.h b/tools/rootcanal/model/hci/h4_parser.h
index 694448c..1a85760 100644
--- a/tools/rootcanal/model/hci/h4_parser.h
+++ b/tools/rootcanal/model/hci/h4_parser.h
@@ -45,14 +45,14 @@
// The parser keeps internal state and is not thread safe.
class H4Parser {
public:
- enum State { HCI_TYPE, HCI_PREAMBLE, HCI_PAYLOAD };
+ enum State { HCI_TYPE, HCI_PREAMBLE, HCI_PAYLOAD, HCI_RECOVERY };
H4Parser(PacketReadCallback command_cb, PacketReadCallback event_cb,
PacketReadCallback acl_cb, PacketReadCallback sco_cb,
- PacketReadCallback iso_cb);
+ PacketReadCallback iso_cb, bool enable_recovery_state = false);
// Consumes the given number of bytes, returns true on success.
- bool Consume(uint8_t* buffer, int32_t bytes);
+ bool Consume(const uint8_t* buffer, int32_t bytes);
// The maximum number of bytes the parser can consume in the current state.
size_t BytesRequested();
@@ -62,13 +62,15 @@
State CurrentState() { return state_; };
+ void EnableRecovery() { enable_recovery_state_ = true; }
+ void DisableRecovery() { enable_recovery_state_ = false; }
+
private:
void OnPacketReady();
// 2 bytes for opcode, 1 byte for parameter length (Volume 2, Part E, 5.4.1)
static constexpr size_t COMMAND_PREAMBLE_SIZE = 3;
static constexpr size_t COMMAND_LENGTH_OFFSET = 2;
-
// 2 bytes for handle, 2 bytes for data length (Volume 2, Part E, 5.4.2)
static constexpr size_t ACL_PREAMBLE_SIZE = 4;
static constexpr size_t ACL_LENGTH_OFFSET = 2;
@@ -101,13 +103,28 @@
uint8_t packet_type_{};
std::vector<uint8_t> packet_;
size_t bytes_wanted_{0};
+ bool enable_recovery_state_{false};
};
inline std::ostream& operator<<(std::ostream& os,
H4Parser::State const& state_) {
- os << (state_ == H4Parser::State::HCI_TYPE ? "HCI_TYPE"
- : state_ == H4Parser::State::HCI_PREAMBLE ? "HCI_PREAMBLE"
- : "HCI_PAYLOAD");
+ switch (state_) {
+ case H4Parser::State::HCI_TYPE:
+ os << "HCI_TYPE";
+ break;
+ case H4Parser::State::HCI_PREAMBLE:
+ os << "HCI_PREAMBLE";
+ break;
+ case H4Parser::State::HCI_PAYLOAD:
+ os << "HCI_PAYLOAD";
+ break;
+ case H4Parser::State::HCI_RECOVERY:
+ os << "HCI_RECOVERY";
+ break;
+ default:
+ os << "unknown state " << static_cast<int>(state_);
+ break;
+ }
return os;
}
diff --git a/tools/rootcanal/test/h4_parser_unittest.cc b/tools/rootcanal/test/h4_parser_unittest.cc
index 07a80c5..9e4b59e 100644
--- a/tools/rootcanal/test/h4_parser_unittest.cc
+++ b/tools/rootcanal/test/h4_parser_unittest.cc
@@ -18,6 +18,8 @@
#include <gtest/gtest.h>
+#include <array>
+
namespace rootcanal {
using PacketData = std::vector<uint8_t>;
@@ -57,6 +59,7 @@
type_ = PacketType::ISO;
PacketReadCallback(p);
},
+ true,
};
PacketData packet_;
PacketType type_;
@@ -109,9 +112,10 @@
}
TEST_F(H4ParserTest, WrongTypeIsDeath) {
+ parser_.DisableRecovery();
PacketData bad_bit({0xfd});
ASSERT_DEATH(parser_.Consume(bad_bit.data(), bad_bit.size()),
- "Unimplemented packet type.*");
+ "Received invalid packet type.*");
}
TEST_F(H4ParserTest, CallsTheRightCallbacks) {
@@ -139,4 +143,43 @@
}
}
+TEST_F(H4ParserTest, Recovery) {
+ // Validate that the recovery state is exited only after receiving the
+ // HCI Reset command.
+ parser_.EnableRecovery();
+
+ // Enter recovery state after receiving an invalid packet type.
+ uint8_t invalid_packet_type = 0xfd;
+ ASSERT_TRUE(parser_.Consume(&invalid_packet_type, 1));
+ ASSERT_EQ(parser_.CurrentState(), H4Parser::State::HCI_RECOVERY);
+
+ const std::array<uint8_t, 4> reset_command{0x01, 0x03, 0x0c, 0x00};
+
+ // Send prefixes of the HCI Reset command, restarting over from the start.
+ for (size_t n = 1; n < 4; n++) {
+ for (size_t i = 0; i < n; i++) {
+ ASSERT_TRUE(parser_.Consume(&reset_command[i], 1));
+ ASSERT_EQ(parser_.CurrentState(), H4Parser::State::HCI_RECOVERY);
+ }
+ }
+
+ // Finally send the full HCI Reset command.
+ for (size_t i = 0; i < 4; i++) {
+ ASSERT_EQ(parser_.CurrentState(), H4Parser::State::HCI_RECOVERY);
+ ASSERT_TRUE(parser_.Consume(&reset_command[i], 1));
+ }
+
+ // Validate that the HCI recovery state is exited,
+ // and the HCI Reset command correctly received on the command callback.
+ ASSERT_EQ(parser_.CurrentState(), H4Parser::State::HCI_TYPE);
+ ASSERT_LT(0, (int)packet_.size());
+
+ // Validate that the HCI Reset command was correctly received.
+ ASSERT_EQ(type_, PacketType::COMMAND);
+ ASSERT_EQ(packet_.size(), reset_command.size() - 1);
+ for (size_t i = 1; i < packet_.size(); i++) {
+ ASSERT_EQ(packet_[i - 1], reset_command[i]);
+ }
+}
+
} // namespace rootcanal