Merge "Reduce test flakyness" into tm-qpr-dev
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 65f2fef..d398fd8 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -70,6 +70,50 @@
},
{
"name" : "net_test_stack_btm"
+ },
+ {
+ "name" : "BluetoothInstrumentationTests",
+ "options" : [{
+ // b/259422308
+ "exclude-filter" : "com.android.bluetooth.opp.BluetoothOppTransferTest#eventHandler_handleMessage_MSG_CONNECT_TIMEOUT"
+ }, {
+ // b/259422308
+ "exclude-filter" : "com.android.bluetooth.opp.BluetoothOppTransferTest#eventHandler_handleMessage_MSG_SHARE_INTERRUPTED_batchFailed"
+ }, {
+ // b/259422308
+ "exclude-filter" : "com.android.bluetooth.opp.BluetoothOppTransferTest#eventHandler_handleMessage_TRANSPORT_ERROR_connectThreadIsNull"
+ }, {
+ // b/259422308
+ "exclude-filter" : "com.android.bluetooth.opp.BluetoothOppTransferTest#onBatchCanceled_checkStatus"
+ }, {
+ // b/259422308
+ "exclude-filter" : "com.android.bluetooth.opp.BluetoothOppUtilityTest#openReceivedFile_fileNotExist"
+ }]
+ },
+ {
+ "name" : "GoogleBluetoothInstrumentationTests",
+ "options" : [{
+ // b/259422308
+ "exclude-filter" : "com.android.bluetooth.opp.BluetoothOppTransferTest#eventHandler_handleMessage_MSG_CONNECT_TIMEOUT"
+ }, {
+ // b/259422308
+ "exclude-filter" : "com.android.bluetooth.opp.BluetoothOppTransferTest#eventHandler_handleMessage_MSG_SHARE_INTERRUPTED_batchFailed"
+ }, {
+ // b/259422308
+ "exclude-filter" : "com.android.bluetooth.opp.BluetoothOppTransferTest#eventHandler_handleMessage_TRANSPORT_ERROR_connectThreadIsNull"
+ }, {
+ // b/259422308
+ "exclude-filter" : "com.android.bluetooth.opp.BluetoothOppTransferTest#onBatchCanceled_checkStatus"
+ }, {
+ // b/259422308
+ "exclude-filter" : "com.android.bluetooth.opp.BluetoothOppUtilityTest#openReceivedFile_fileNotExist"
+ }]
+ },
+ {
+ "name" : "FrameworkBluetoothTests"
+ },
+ {
+ "name" : "ServiceBluetoothTests"
}
]
}
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/config.xml b/android/app/res/values/config.xml
index ecc240d..913c77f 100644
--- a/android/app/res/values/config.xml
+++ b/android/app/res/values/config.xml
@@ -62,9 +62,6 @@
<!-- For enabling browsed cover art with the AVRCP Controller Cover Artwork feature -->
<bool name="avrcp_controller_cover_art_browsed_images">false</bool>
- <!-- For enabling the hfp client connection service -->
- <bool name="hfp_client_connection_service_enabled">false</bool>
-
<!-- For supporting emergency call through the hfp client connection service -->
<bool name="hfp_client_connection_service_support_emergency_call">true</bool>
diff --git a/android/app/src/com/android/bluetooth/BluetoothMethodProxy.java b/android/app/src/com/android/bluetooth/BluetoothMethodProxy.java
index 3f7f13b..d1ea7d4 100644
--- a/android/app/src/com/android/bluetooth/BluetoothMethodProxy.java
+++ b/android/app/src/com/android/bluetooth/BluetoothMethodProxy.java
@@ -16,12 +16,14 @@
package com.android.bluetooth;
+import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.le.PeriodicAdvertisingCallback;
import android.bluetooth.le.PeriodicAdvertisingManager;
import android.bluetooth.le.ScanResult;
+import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
@@ -162,6 +164,14 @@
}
/**
+ * Proxies {@link ContentResolver#acquireUnstableContentProviderClient(String)}.
+ */
+ public ContentProviderClient contentResolverAcquireUnstableContentProviderClient(
+ ContentResolver contentResolver, @NonNull String name) {
+ return contentResolver.acquireUnstableContentProviderClient(name);
+ }
+
+ /**
* Proxies {@link Context#sendBroadcast(Intent)}.
*/
public void contextSendBroadcast(Context context, @RequiresPermission Intent intent) {
diff --git a/android/app/src/com/android/bluetooth/Utils.java b/android/app/src/com/android/bluetooth/Utils.java
index fd0a2f1..b22b3aa 100644
--- a/android/app/src/com/android/bluetooth/Utils.java
+++ b/android/app/src/com/android/bluetooth/Utils.java
@@ -408,9 +408,6 @@
@RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
public static void enforceBluetoothPrivilegedPermission(Context context) {
- if (isInstrumentationTestMode()) {
- return;
- }
context.enforceCallingOrSelfPermission(
android.Manifest.permission.BLUETOOTH_PRIVILEGED,
"Need BLUETOOTH PRIVILEGED permission");
@@ -418,9 +415,6 @@
@RequiresPermission(android.Manifest.permission.LOCAL_MAC_ADDRESS)
public static void enforceLocalMacAddressPermission(Context context) {
- if (isInstrumentationTestMode()) {
- return;
- }
context.enforceCallingOrSelfPermission(
android.Manifest.permission.LOCAL_MAC_ADDRESS,
"Need LOCAL_MAC_ADDRESS permission");
@@ -428,9 +422,6 @@
@RequiresPermission(android.Manifest.permission.DUMP)
public static void enforceDumpPermission(Context context) {
- if (isInstrumentationTestMode()) {
- return;
- }
context.enforceCallingOrSelfPermission(
android.Manifest.permission.DUMP,
"Need DUMP permission");
@@ -1017,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/bas/BatteryStateMachine.java b/android/app/src/com/android/bluetooth/bas/BatteryStateMachine.java
index 878c71e..394f417 100644
--- a/android/app/src/com/android/bluetooth/bas/BatteryStateMachine.java
+++ b/android/app/src/com/android/bluetooth/bas/BatteryStateMachine.java
@@ -18,7 +18,7 @@
import static android.bluetooth.BluetoothDevice.PHY_LE_1M_MASK;
import static android.bluetooth.BluetoothDevice.PHY_LE_2M_MASK;
-import static android.bluetooth.BluetoothDevice.TRANSPORT_AUTO;
+import static android.bluetooth.BluetoothDevice.TRANSPORT_LE;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
@@ -233,7 +233,7 @@
mBluetoothGatt.close();
}
mBluetoothGatt = mDevice.connectGatt(service, /*autoConnect=*/false,
- mGattCallback, TRANSPORT_AUTO, /*opportunistic=*/true,
+ mGattCallback, TRANSPORT_LE, /*opportunistic=*/true,
PHY_LE_1M_MASK | PHY_LE_2M_MASK, getHandler());
return mBluetoothGatt != null;
}
diff --git a/android/app/src/com/android/bluetooth/btservice/AdapterService.java b/android/app/src/com/android/bluetooth/btservice/AdapterService.java
index 29e0692..c5fb78b 100644
--- a/android/app/src/com/android/bluetooth/btservice/AdapterService.java
+++ b/android/app/src/com/android/bluetooth/btservice/AdapterService.java
@@ -247,13 +247,7 @@
return sAdapterService;
}
- /**
- * Sets AdapterService for testing.
- *
- * @hide
- */
- @VisibleForTesting
- public static synchronized void setAdapterService(AdapterService instance) {
+ private static synchronized void setAdapterService(AdapterService instance) {
Log.d(TAG, "setAdapterService() - trying to set service to " + instance);
if (instance == null) {
return;
@@ -4656,6 +4650,8 @@
return BluetoothStatusCodes.ERROR_DISCONNECT_REASON_BAD_PARAMETERS;
case /*HCI_ERR_PEER_USER*/ 0x13:
return BluetoothStatusCodes.ERROR_DISCONNECT_REASON_REMOTE_REQUEST;
+ case /*HCI_ERR_REMOTE_POWER_OFF*/ 0x15:
+ return BluetoothStatusCodes.ERROR_DISCONNECT_REASON_REMOTE_REQUEST;
case /*HCI_ERR_CONN_CAUSE_LOCAL_HOST*/ 0x16:
return BluetoothStatusCodes.ERROR_DISCONNECT_REASON_LOCAL_REQUEST;
case /*HCI_ERR_UNSUPPORTED_REM_FEATURE*/ 0x1A:
diff --git a/android/app/src/com/android/bluetooth/btservice/RemoteDevices.java b/android/app/src/com/android/bluetooth/btservice/RemoteDevices.java
index 90d10ee..3fb1080 100644
--- a/android/app/src/com/android/bluetooth/btservice/RemoteDevices.java
+++ b/android/app/src/com/android/bluetooth/btservice/RemoteDevices.java
@@ -38,6 +38,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;
@@ -754,6 +755,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/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..21f596d 100644
--- a/android/app/src/com/android/bluetooth/gatt/GattService.java
+++ b/android/app/src/com/android/bluetooth/gatt/GattService.java
@@ -145,7 +145,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 +275,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 +313,8 @@
/**
* Reliable write queue
*/
- private Set<String> mReliableQueue = new HashSet<String>();
+ @VisibleForTesting
+ Set<String> mReliableQueue = new HashSet<String>();
static {
classInitNative();
@@ -4581,7 +4586,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;
}
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/le_audio/LeAudioService.java b/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java
index 42243ab..8eab0d5 100644
--- a/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java
+++ b/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java
@@ -339,7 +339,6 @@
mActiveAudioOutDevice = null;
mActiveAudioInDevice = null;
- mDatabaseManager = null;
mLeAudioCodecConfig = null;
// Set the service and BLE devices as inactive
diff --git a/android/app/src/com/android/bluetooth/map/BluetoothMapContent.java b/android/app/src/com/android/bluetooth/map/BluetoothMapContent.java
index e0d202e..19c7911 100644
--- a/android/app/src/com/android/bluetooth/map/BluetoothMapContent.java
+++ b/android/app/src/com/android/bluetooth/map/BluetoothMapContent.java
@@ -73,10 +73,13 @@
private static final int MASK_SUBJECT = 0x00000001;
@VisibleForTesting
static final int MASK_DATETIME = 0x00000002;
- private static final int MASK_SENDER_NAME = 0x00000004;
- private static final int MASK_SENDER_ADDRESSING = 0x00000008;
+ @VisibleForTesting
+ static final int MASK_SENDER_NAME = 0x00000004;
+ @VisibleForTesting
+ static final int MASK_SENDER_ADDRESSING = 0x00000008;
private static final int MASK_RECIPIENT_NAME = 0x00000010;
- private static final int MASK_RECIPIENT_ADDRESSING = 0x00000020;
+ @VisibleForTesting
+ static final int MASK_RECIPIENT_ADDRESSING = 0x00000020;
private static final int MASK_TYPE = 0x00000040;
private static final int MASK_SIZE = 0x00000080;
private static final int MASK_RECEPTION_STATUS = 0x00000100;
@@ -154,7 +157,8 @@
private final BluetoothMapAccountItem mAccount;
/* The MasInstance reference is used to update persistent (over a connection) version counters*/
private final BluetoothMapMasInstance mMasInstance;
- private String mMessageVersion = BluetoothMapUtils.MAP_V10_STR;
+ @VisibleForTesting
+ String mMessageVersion = BluetoothMapUtils.MAP_V10_STR;
private int mRemoteFeatureMask = BluetoothMapUtils.MAP_FEATURE_DEFAULT_BITMASK;
@VisibleForTesting
@@ -219,7 +223,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,
@@ -999,7 +1004,8 @@
return sb.toString();
}
- private void setRecipientAddressing(BluetoothMapMessageListingElement e, Cursor c,
+ @VisibleForTesting
+ void setRecipientAddressing(BluetoothMapMessageListingElement e, Cursor c,
FilterInfo fi, BluetoothMapAppParams ap) {
if ((ap.getParameterMask() & MASK_RECIPIENT_ADDRESSING) != 0) {
String address = null;
@@ -1079,7 +1085,8 @@
}
}
- private void setSenderAddressing(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi,
+ @VisibleForTesting
+ void setSenderAddressing(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi,
BluetoothMapAppParams ap) {
if ((ap.getParameterMask() & MASK_SENDER_ADDRESSING) != 0) {
String address = "";
@@ -1146,10 +1153,10 @@
// TODO: This is a BAD hack, that we map the contact ID to a conversation ID!!!
// We need to reach a conclusion on what to do
Uri contactsUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_CONVOCONTACT);
- Cursor contacts =
- mResolver.query(contactsUri, BluetoothMapContract.BT_CONTACT_PROJECTION,
- BluetoothMapContract.ConvoContactColumns.CONVO_ID + " = "
- + contactId, null, null);
+ Cursor contacts = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
+ contactsUri, BluetoothMapContract.BT_CONTACT_PROJECTION,
+ BluetoothMapContract.ConvoContactColumns.CONVO_ID + " = " + contactId, null,
+ null);
try {
// TODO this will not work for group-chats
if (contacts != null && contacts.moveToFirst()) {
@@ -1173,7 +1180,8 @@
}
}
- private void setSenderName(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi,
+ @VisibleForTesting
+ void setSenderName(BluetoothMapMessageListingElement e, Cursor c, FilterInfo fi,
BluetoothMapAppParams ap) {
if ((ap.getParameterMask() & MASK_SENDER_NAME) != 0) {
String name = "";
@@ -1227,10 +1235,10 @@
// For IM we add the contact ID in the addressing
long contactId = c.getLong(fi.mMessageColFromAddress);
Uri contactsUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_CONVOCONTACT);
- Cursor contacts =
- mResolver.query(contactsUri, BluetoothMapContract.BT_CONTACT_PROJECTION,
- BluetoothMapContract.ConvoContactColumns.CONVO_ID + " = "
- + contactId, null, null);
+ Cursor contacts = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
+ contactsUri, BluetoothMapContract.BT_CONTACT_PROJECTION,
+ BluetoothMapContract.ConvoContactColumns.CONVO_ID + " = " + contactId, null,
+ null);
try {
// TODO this will not work for group-chats
if (contacts != null && contacts.moveToFirst()) {
@@ -1278,9 +1286,8 @@
}
}
-
- private void setLastActivity(BluetoothMapConvoListingElement e, Cursor c, FilterInfo fi,
- BluetoothMapAppParams ap) {
+ @VisibleForTesting
+ void setLastActivity(BluetoothMapConvoListingElement e, Cursor c, FilterInfo fi) {
long date = 0;
if (fi.mMsgType == FilterInfo.TYPE_SMS || fi.mMsgType == FilterInfo.TYPE_MMS) {
date = c.getLong(MMS_SMS_THREAD_COL_DATE);
@@ -1392,7 +1399,7 @@
private BluetoothMapConvoListingElement createConvoElement(Cursor c, FilterInfo fi,
BluetoothMapAppParams ap) {
BluetoothMapConvoListingElement e = new BluetoothMapConvoListingElement();
- setLastActivity(e, c, fi, ap);
+ setLastActivity(e, c, fi);
e.setType(getType(c, fi));
// setConvoRead(e, c, fi, ap);
e.setCursorIndex(c.getPosition());
@@ -2788,7 +2795,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)
@@ -2849,13 +2857,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)
@@ -4087,8 +4094,8 @@
BluetoothMapbMessageEmail message = new BluetoothMapbMessageEmail();
Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
- Cursor c = mResolver.query(contentUri, BluetoothMapContract.BT_MESSAGE_PROJECTION,
- "_ID = " + id, null, null);
+ Cursor c = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver, contentUri,
+ BluetoothMapContract.BT_MESSAGE_PROJECTION, "_ID = " + id, null, null);
try {
if (c != null && c.moveToFirst()) {
BluetoothMapFolderElement folderElement;
@@ -4185,7 +4192,8 @@
// Get email message body content
int count = 0;
try {
- fd = mResolver.openFileDescriptor(uri, "r");
+ fd = BluetoothMethodProxy.getInstance().contentResolverOpenFileDescriptor(
+ mResolver, uri, "r");
is = new FileInputStream(fd.getFileDescriptor());
StringBuilder email = new StringBuilder("");
byte[] buffer = new byte[1024];
@@ -4256,8 +4264,8 @@
BluetoothMapbMessageMime message = new BluetoothMapbMessageMime();
Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
- Cursor c = mResolver.query(contentUri, BluetoothMapContract.BT_MESSAGE_PROJECTION,
- "_ID = " + id, null, null);
+ Cursor c = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver, contentUri,
+ BluetoothMapContract.BT_MESSAGE_PROJECTION, "_ID = " + id, null, null);
Cursor contacts = null;
try {
if (c != null && c.moveToFirst()) {
@@ -4310,7 +4318,8 @@
// FIXME end temp code
Uri contactsUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_CONVOCONTACT);
- contacts = mResolver.query(contactsUri, BluetoothMapContract.BT_CONTACT_PROJECTION,
+ contacts = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
+ contactsUri, BluetoothMapContract.BT_CONTACT_PROJECTION,
BluetoothMapContract.ConvoContactColumns.CONVO_ID + " = " + threadId, null,
null);
// TODO this will not work for group-chats
diff --git a/android/app/src/com/android/bluetooth/map/BluetoothMapContentObserver.java b/android/app/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
index a2af6f2..1862f33 100644
--- a/android/app/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
+++ b/android/app/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
@@ -142,18 +142,23 @@
private BluetoothMapMasInstance mMasInstance = null;
private int mMasId;
private boolean mEnableSmsMms = false;
- private boolean mObserverRegistered = false;
- private BluetoothMapAccountItem mAccount;
- private String mAuthority = null;
+ @VisibleForTesting
+ boolean mObserverRegistered = false;
+ @VisibleForTesting
+ BluetoothMapAccountItem mAccount;
+ @VisibleForTesting
+ String mAuthority = null;
// Default supported feature bit mask is 0x1f
private int mMapSupportedFeatures = BluetoothMapUtils.MAP_FEATURE_DEFAULT_BITMASK;
// Default event report version is 1.0
- private int mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V10;
+ @VisibleForTesting
+ int mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V10;
private BluetoothMapFolderElement mFolders = new BluetoothMapFolderElement("DUMMY", null);
// Will be set by the MAS when generated.
- private Uri mMessageUri = null;
+ @VisibleForTesting
+ Uri mMessageUri = null;
@VisibleForTesting
Uri mContactUri = null;
@@ -1456,8 +1461,14 @@
if (mTransmitEvents && // extract contact details only if needed
mMapEventReportVersion
> BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
- String date = BluetoothMapUtils.getDateTimeString(
- c.getLong(c.getColumnIndex(Sms.DATE)));
+ 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
+ listChanged = false;
+ msgListSms.remove(id);
+ continue;
+ }
String subject = c.getString(c.getColumnIndex(Sms.BODY));
if (subject == null) {
subject = "";
@@ -1574,7 +1585,8 @@
}
}
- private void handleMsgListChangesMms() {
+ @VisibleForTesting
+ void handleMsgListChangesMms() {
if (V) {
Log.d(TAG, "handleMsgListChangesMms");
}
@@ -1584,9 +1596,11 @@
Cursor c;
synchronized (getMsgListMms()) {
if (mMapEventReportVersion == BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
- 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);
} else {
- c = mResolver.query(Mms.CONTENT_URI, MMS_PROJECTION_SHORT_EXT, null, null, null);
+ c = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
+ Mms.CONTENT_URI, MMS_PROJECTION_SHORT_EXT, null, null, null);
}
try {
@@ -1625,8 +1639,14 @@
if (mTransmitEvents && // extract contact details only if needed
mMapEventReportVersion
!= BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
- String date = BluetoothMapUtils.getDateTimeString(
- c.getLong(c.getColumnIndex(Mms.DATE)));
+ long timestamp = 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;
+ }
String subject = c.getString(c.getColumnIndex(Mms.SUBJECT));
if (subject == null || subject.length() == 0) {
/* Get subject from mms text body parts - if any exists */
@@ -1742,7 +1762,8 @@
}
}
- private void handleMsgListChangesMsg(Uri uri) throws RemoteException {
+ @VisibleForTesting
+ void handleMsgListChangesMsg(Uri uri) throws RemoteException {
if (V) {
Log.v(TAG, "handleMsgListChangesMsg uri: " + uri.toString());
}
@@ -1855,7 +1876,8 @@
&& sentFolder.getFolderId() == folderId
&& msg.localInitiatedSend) {
if (msg.transparent) {
- mResolver.delete(
+ BluetoothMethodProxy.getInstance().contentResolverDelete(
+ mResolver,
ContentUris.withAppendedId(mMessageUri, id), null,
null);
} else {
@@ -1955,7 +1977,8 @@
}
}
- private void handleContactListChanges(Uri uri) {
+ @VisibleForTesting
+ void handleContactListChanges(Uri uri) {
if (uri.getAuthority().equals(mAuthority)) {
try {
if (V) {
@@ -2931,7 +2954,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) {
@@ -2939,7 +2963,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));
}
@@ -3558,7 +3583,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);
@@ -3633,7 +3658,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/BluetoothMapMasInstance.java b/android/app/src/com/android/bluetooth/map/BluetoothMapMasInstance.java
index 43daaf8..2567da6 100644
--- a/android/app/src/com/android/bluetooth/map/BluetoothMapMasInstance.java
+++ b/android/app/src/com/android/bluetooth/map/BluetoothMapMasInstance.java
@@ -30,6 +30,7 @@
import com.android.bluetooth.map.BluetoothMapContentObserver.Msg;
import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
import com.android.bluetooth.sdp.SdpManager;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.obex.ServerSession;
import java.io.IOException;
@@ -39,8 +40,10 @@
import java.util.concurrent.atomic.AtomicLong;
public class BluetoothMapMasInstance implements IObexConnectionHandler {
- private final String mTag;
- private static volatile int sInstanceCounter = 0;
+ @VisibleForTesting
+ final String mTag;
+ @VisibleForTesting
+ static volatile int sInstanceCounter = 0;
private static final boolean D = BluetoothMapService.DEBUG;
private static final boolean V = BluetoothMapService.VERBOSE;
@@ -146,7 +149,8 @@
}
/* Needed only for test */
- protected BluetoothMapMasInstance() {
+ @VisibleForTesting
+ BluetoothMapMasInstance() {
mTag = "BluetoothMapMasInstance" + sInstanceCounter++;
}
diff --git a/android/app/src/com/android/bluetooth/map/BluetoothMapObexServer.java b/android/app/src/com/android/bluetooth/map/BluetoothMapObexServer.java
index 911fc91..f855c78 100644
--- a/android/app/src/com/android/bluetooth/map/BluetoothMapObexServer.java
+++ b/android/app/src/com/android/bluetooth/map/BluetoothMapObexServer.java
@@ -29,9 +29,11 @@
import android.text.format.DateUtils;
import android.util.Log;
+import com.android.bluetooth.BluetoothMethodProxy;
import com.android.bluetooth.SignedLongLong;
import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
import com.android.bluetooth.mapapi.BluetoothMapContract;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.obex.HeaderSet;
import com.android.obex.Operation;
import com.android.obex.ResponseCodes;
@@ -168,8 +170,8 @@
*
*/
private ContentProviderClient acquireUnstableContentProviderOrThrow() throws RemoteException {
- ContentProviderClient providerClient =
- mResolver.acquireUnstableContentProviderClient(mAuthority);
+ ContentProviderClient providerClient = BluetoothMethodProxy.getInstance()
+ .contentResolverAcquireUnstableContentProviderClient(mResolver, mAuthority);
if (providerClient == null) {
throw new RemoteException("Failed to acquire provider for " + mAuthority);
}
@@ -276,7 +278,8 @@
* folder.getFolderId() will be used to query sub-folders.
* Use a parentFolder with id -1 to get all folders from root.
*/
- private void addEmailFolders(BluetoothMapFolderElement parentFolder) throws RemoteException {
+ @VisibleForTesting
+ void addEmailFolders(BluetoothMapFolderElement parentFolder) throws RemoteException {
// Select all parent folders
BluetoothMapFolderElement newFolder;
@@ -529,7 +532,7 @@
+ appParams.getChatState() + ", ChatStatusConvoId: "
+ appParams.getChatStateConvoIdString());
}
- return setOwnerStatus(name, appParams);
+ return setOwnerStatus(appParams);
}
} catch (RemoteException e) {
@@ -841,7 +844,8 @@
return ResponseCodes.OBEX_HTTP_OK;
}
- private int setOwnerStatus(String msgHandle, BluetoothMapAppParams appParams)
+ @VisibleForTesting
+ int setOwnerStatus(BluetoothMapAppParams appParams)
throws RemoteException {
// This does only work for IM
if (mAccount != null && mAccount.getType() == BluetoothMapUtils.TYPE.IM) {
@@ -1845,7 +1849,7 @@
+ appParams.getChatState() + ", ChatStatusConvoId: "
+ appParams.getChatStateConvoIdString());
}
- return setOwnerStatus(name, appParams);
+ return setOwnerStatus(appParams);
}
} catch (RemoteException e) {
diff --git a/android/app/src/com/android/bluetooth/map/BluetoothMapService.java b/android/app/src/com/android/bluetooth/map/BluetoothMapService.java
index 7a00e8a..e4dfe64 100644
--- a/android/app/src/com/android/bluetooth/map/BluetoothMapService.java
+++ b/android/app/src/com/android/bluetooth/map/BluetoothMapService.java
@@ -104,10 +104,12 @@
static final int MSG_OBSERVER_REGISTRATION = 5008;
private static final int START_LISTENER = 1;
- private static final int USER_TIMEOUT = 2;
+ @VisibleForTesting
+ static final int USER_TIMEOUT = 2;
private static final int DISCONNECT_MAP = 3;
private static final int SHUTDOWN = 4;
- private static final int UPDATE_MAS_INSTANCES = 5;
+ @VisibleForTesting
+ static final int UPDATE_MAS_INSTANCES = 5;
private static final int RELEASE_WAKE_LOCK_DELAY = 10000;
private PowerManager.WakeLock mWakeLock = null;
@@ -148,7 +150,8 @@
private boolean mAccountChanged = false;
private boolean mSdpSearchInitiated = false;
private SdpMnsRecord mMnsRecord = null;
- private MapServiceMessageHandler mSessionStatusHandler;
+ @VisibleForTesting
+ Handler mSessionStatusHandler;
private boolean mServiceStarted = false;
private static BluetoothMapService sBluetoothMapService;
@@ -834,7 +837,8 @@
* If the key 255 is in use, the first free masId will be returned.
* @return a free MasId
*/
- private int getNextMasId() {
+ @VisibleForTesting
+ int getNextMasId() {
// Find the largest masId in use
int largestMasId = 0;
for (int i = 0, c = mMasInstances.size(); i < c; i++) {
@@ -1026,7 +1030,8 @@
} // Can only be null during shutdown
}
- private void sendConnectTimeoutMessage() {
+ @VisibleForTesting
+ void sendConnectTimeoutMessage() {
if (DEBUG) {
Log.d(TAG, "sendConnectTimeoutMessage()");
}
@@ -1036,7 +1041,8 @@
} // Can only be null during shutdown
}
- private void sendConnectCancelMessage() {
+ @VisibleForTesting
+ void sendConnectCancelMessage() {
if (mSessionStatusHandler != null) {
Message msg = mSessionStatusHandler.obtainMessage(MSG_MAS_CONNECT_CANCEL);
msg.sendToTarget();
diff --git a/android/app/src/com/android/bluetooth/map/BluetoothMapUtils.java b/android/app/src/com/android/bluetooth/map/BluetoothMapUtils.java
index a3710a3..7d7047f 100644
--- a/android/app/src/com/android/bluetooth/map/BluetoothMapUtils.java
+++ b/android/app/src/com/android/bluetooth/map/BluetoothMapUtils.java
@@ -677,6 +677,21 @@
return format.format(cal.getTime());
}
+ static boolean isDateTimeOlderThanOneYear(long timestamp) {
+ Calendar cal = Calendar.getInstance();
+ cal.setTimeInMillis(timestamp);
+ Calendar oneYearAgo = Calendar.getInstance();
+ oneYearAgo.add(Calendar.YEAR, -1);
+ if (cal.compareTo(oneYearAgo) > 0) {
+ if (V) {
+ Log.v(TAG, "isDateTimeOlderThanOneYear timestamp : " + timestamp
+ + " oneYearAgo: " + oneYearAgo);
+ }
+ return true;
+ }
+ return false;
+ }
+
static void savePeerSupportUtcTimeStamp(int remoteFeatureMask) {
if ((remoteFeatureMask & MAP_FEATURE_DEFINED_TIMESTAMP_FORMAT_BIT)
== MAP_FEATURE_DEFINED_TIMESTAMP_FORMAT_BIT) {
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/MapClientService.java b/android/app/src/com/android/bluetooth/mapclient/MapClientService.java
index 40bf81c..ff38353 100644
--- a/android/app/src/com/android/bluetooth/mapclient/MapClientService.java
+++ b/android/app/src/com/android/bluetooth/mapclient/MapClientService.java
@@ -166,6 +166,9 @@
}
private synchronized void addDeviceToMapAndConnect(BluetoothDevice device) {
+ if (Utils.isInstrumentationTestMode()) {
+ Log.d(TAG, "addDeviceToMapAndConnect: device=" + device, new Exception());
+ }
// When creating a new statemachine, its state is set to CONNECTING - which will trigger
// connect.
MceStateMachine mapStateMachine = new MceStateMachine(this, device);
@@ -365,6 +368,9 @@
}
removeUncleanAccounts();
mMapInstanceMap.clear();
+ if (Utils.isInstrumentationTestMode()) {
+ Log.d(TAG, "cleanup() called.", new Exception());
+ }
// 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/pan/PanService.java b/android/app/src/com/android/bluetooth/pan/PanService.java
index a1b73e8..93a4a0c 100644
--- a/android/app/src/com/android/bluetooth/pan/PanService.java
+++ b/android/app/src/com/android/bluetooth/pan/PanService.java
@@ -70,10 +70,12 @@
private static final int BLUETOOTH_MAX_PAN_CONNECTIONS = 5;
private static final int BLUETOOTH_PREFIX_LENGTH = 24;
- private HashMap<BluetoothDevice, BluetoothPanDevice> mPanDevices;
+ @VisibleForTesting
+ HashMap<BluetoothDevice, BluetoothPanDevice> mPanDevices;
private int mMaxPanDevices;
private String mPanIfName;
- private boolean mIsTethering = false;
+ @VisibleForTesting
+ boolean mIsTethering = false;
private boolean mNativeAvailable;
private HashMap<String, IBluetoothPanCallback> mBluetoothTetheringCallbacks;
@@ -602,9 +604,8 @@
public int remote_role;
}
- ;
-
- private void onConnectStateChanged(byte[] address, int state, int error, int localRole,
+ @VisibleForTesting
+ void onConnectStateChanged(byte[] address, int state, int error, int localRole,
int remoteRole) {
if (DBG) {
Log.d(TAG, "onConnectStateChanged: " + state + ", local role:" + localRole
@@ -615,7 +616,8 @@
mHandler.sendMessage(msg);
}
- private void onControlStateChanged(int localRole, int state, int error, String ifname) {
+ @VisibleForTesting
+ void onControlStateChanged(int localRole, int state, int error, String ifname) {
if (DBG) {
Log.d(TAG, "onControlStateChanged: " + state + ", error: " + error + ", ifname: "
+ ifname);
@@ -625,7 +627,8 @@
}
}
- private static int convertHalState(int halState) {
+ @VisibleForTesting
+ static int convertHalState(int halState) {
switch (halState) {
case CONN_STATE_CONNECTED:
return BluetoothProfile.STATE_CONNECTED;
@@ -748,25 +751,6 @@
sendBroadcast(intent, BLUETOOTH_CONNECT);
}
- private List<BluetoothDevice> getConnectedPanDevices() {
- List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
-
- for (BluetoothDevice device : mPanDevices.keySet()) {
- if (getPanDeviceConnectionState(device) == BluetoothProfile.STATE_CONNECTED) {
- devices.add(device);
- }
- }
- return devices;
- }
-
- private int getPanDeviceConnectionState(BluetoothDevice device) {
- BluetoothPanDevice panDevice = mPanDevices.get(device);
- if (panDevice == null) {
- return BluetoothProfile.STATE_DISCONNECTED;
- }
- return panDevice.mState;
- }
-
@Override
public void dump(StringBuilder sb) {
super.dump(sb);
@@ -779,7 +763,8 @@
}
}
- private class BluetoothPanDevice {
+ @VisibleForTesting
+ static class BluetoothPanDevice {
private int mState;
private String mIface;
private int mLocalRole; // Which local role is this PAN device bound to
@@ -795,10 +780,14 @@
// Constants matching Hal header file bt_hh.h
// bthh_connection_state_t
- private static final int CONN_STATE_CONNECTED = 0;
- private static final int CONN_STATE_CONNECTING = 1;
- private static final int CONN_STATE_DISCONNECTED = 2;
- private static final int CONN_STATE_DISCONNECTING = 3;
+ @VisibleForTesting
+ static final int CONN_STATE_CONNECTED = 0;
+ @VisibleForTesting
+ static final int CONN_STATE_CONNECTING = 1;
+ @VisibleForTesting
+ static final int CONN_STATE_DISCONNECTED = 2;
+ @VisibleForTesting
+ static final int CONN_STATE_DISCONNECTING = 3;
private static native void classInitNative();
diff --git a/android/app/src/com/android/bluetooth/pbap/BluetoothPbapService.java b/android/app/src/com/android/bluetooth/pbap/BluetoothPbapService.java
index 331fb60..62ebfd5 100644
--- a/android/app/src/com/android/bluetooth/pbap/BluetoothPbapService.java
+++ b/android/app/src/com/android/bluetooth/pbap/BluetoothPbapService.java
@@ -166,7 +166,8 @@
private PbapHandler mSessionStatusHandler;
private HandlerThread mHandlerThread;
- private final HashMap<BluetoothDevice, PbapStateMachine> mPbapStateMachineMap = new HashMap<>();
+ @VisibleForTesting
+ final HashMap<BluetoothDevice, PbapStateMachine> mPbapStateMachineMap = new HashMap<>();
private volatile int mNextNotificationId = PBAP_NOTIFICATION_ID_START;
// package and class name to which we send intent to check phone book access permission
@@ -276,7 +277,8 @@
}
}
- private BroadcastReceiver mPbapReceiver = new BroadcastReceiver() {
+ @VisibleForTesting
+ BroadcastReceiver mPbapReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
parseIntent(intent);
@@ -446,10 +448,6 @@
public int getConnectionState(BluetoothDevice device) {
enforceCallingOrSelfPermission(
BLUETOOTH_PRIVILEGED, "Need BLUETOOTH_PRIVILEGED permission");
- if (mPbapStateMachineMap == null) {
- return BluetoothProfile.STATE_DISCONNECTED;
- }
-
synchronized (mPbapStateMachineMap) {
PbapStateMachine sm = mPbapStateMachineMap.get(device);
if (sm == null) {
@@ -460,9 +458,6 @@
}
List<BluetoothDevice> getConnectedDevices() {
- if (mPbapStateMachineMap == null) {
- return new ArrayList<>();
- }
synchronized (mPbapStateMachineMap) {
return new ArrayList<>(mPbapStateMachineMap.keySet());
}
@@ -470,7 +465,7 @@
List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
List<BluetoothDevice> devices = new ArrayList<>();
- if (mPbapStateMachineMap == null || states == null) {
+ if (states == null) {
return devices;
}
synchronized (mPbapStateMachineMap) {
@@ -631,6 +626,7 @@
getContentResolver().unregisterContentObserver(mContactChangeObserver);
mContactChangeObserver = null;
setComponentAvailable(PBAP_ACTIVITY, false);
+ mPbapStateMachineMap.clear();
return true;
}
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/BluetoothPrefsTest.java b/android/app/tests/unit/src/com/android/bluetooth/BluetoothPrefsTest.java
new file mode 100644
index 0000000..dc8ba43
--- /dev/null
+++ b/android/app/tests/unit/src/com/android/bluetooth/BluetoothPrefsTest.java
@@ -0,0 +1,88 @@
+/*
+ * 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 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.intent.Intents.intended;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.espresso.intent.Intents;
+import androidx.test.espresso.intent.matcher.IntentMatchers;
+import androidx.test.filters.LargeTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class BluetoothPrefsTest {
+
+ Context mTargetContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ Intent mIntent;
+
+ ActivityScenario<BluetoothPrefs> mActivityScenario;
+
+ @Before
+ public void setUp() {
+ Intents.init();
+ enableActivity(true);
+
+ mIntent = new Intent();
+ mIntent.setClass(mTargetContext, BluetoothPrefs.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_launchesBluetoothSettingsActivity() {
+ intended(IntentMatchers.hasAction(BluetoothPrefs.BLUETOOTH_SETTING_ACTION));
+ }
+
+ 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, BluetoothPrefs.class);
+ mTargetContext.getPackageManager().setComponentEnabledSetting(
+ activityName, enabledState, DONT_KILL_APP);
+ }
+}
\ No newline at end of file
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/a2dp/A2dpServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/a2dp/A2dpServiceTest.java
index 416661d..1d216f7 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/a2dp/A2dpServiceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/a2dp/A2dpServiceTest.java
@@ -91,6 +91,7 @@
}
TestUtils.setAdapterService(mAdapterService);
+ doReturn(true).when(mAdapterService).isA2dpOffloadEnabled();
doReturn(MAX_CONNECTED_AUDIO_DEVICES).when(mAdapterService).getMaxConnectedAudioDevices();
doReturn(true, false).when(mAdapterService).isStartedProfile(anyString());
doReturn(false).when(mAdapterService).isQuietModeEnabled();
@@ -865,6 +866,11 @@
verifySupportTime, verifyNotSupportTime, verifyEnabledTime);
}
+ @Test
+ public void testDumpDoesNotCrash() {
+ mA2dpService.dump(new StringBuilder());
+ }
+
private void connectDevice(BluetoothDevice device) {
connectDeviceWithCodecStatus(device, null);
}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/a2dp/A2dpStateMachineTest.java b/android/app/tests/unit/src/com/android/bluetooth/a2dp/A2dpStateMachineTest.java
index e087fe5..d4fe9ed 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/a2dp/A2dpStateMachineTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/a2dp/A2dpStateMachineTest.java
@@ -417,4 +417,22 @@
// Check if low latency audio been update.
verify(mA2dpService, times(6)).updateLowLatencyAudioSupport(mTestDevice);
}
+
+ @Test
+ public void dump_doesNotCrash() {
+ BluetoothCodecConfig[] codecsSelectableSbc;
+ codecsSelectableSbc = new BluetoothCodecConfig[1];
+ codecsSelectableSbc[0] = mCodecConfigSbc;
+
+ BluetoothCodecConfig[] codecsSelectableSbcAac;
+ codecsSelectableSbcAac = new BluetoothCodecConfig[2];
+ codecsSelectableSbcAac[0] = mCodecConfigSbc;
+ codecsSelectableSbcAac[1] = mCodecConfigAac;
+
+ BluetoothCodecStatus codecStatusSbcAndSbc = new BluetoothCodecStatus(mCodecConfigSbc,
+ Arrays.asList(codecsSelectableSbcAac), Arrays.asList(codecsSelectableSbc));
+ mA2dpStateMachine.processCodecConfigEvent(codecStatusSbcAndSbc);
+
+ mA2dpStateMachine.dump(new StringBuilder());
+ }
}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkServiceTest.java
index 42b5ff7..1f63faa 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkServiceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkServiceTest.java
@@ -464,4 +464,12 @@
assertThat(mService.setConnectionPolicy(mDevice1,
BluetoothProfile.CONNECTION_POLICY_ALLOWED)).isFalse();
}
+
+ @Test
+ public void testDumpDoesNotCrash() {
+ mockDevicePriority(mDevice1, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ setupDeviceConnection(mDevice1);
+
+ mService.dump(new StringBuilder());
+ }
}
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..0ef8a29
--- /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()).isFalse();
+ }
+}
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/avrcpcontroller/AvrcpControllerServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerServiceTest.java
index 24d35f5..f4d400b 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerServiceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/avrcpcontroller/AvrcpControllerServiceTest.java
@@ -428,4 +428,10 @@
eq(AvrcpControllerStateMachine.MESSAGE_PROCESS_GET_PLAYER_ITEMS),
eq(new ArrayList<>(Arrays.asList(items))));
}
+
+ @Test
+ public void dump_doesNotCrash() {
+ mService.getRcPsm(REMOTE_DEVICE_ADDRESS_AS_ARRAY, 1);
+ mService.dump(new StringBuilder());
+ }
}
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 ef909ac..8c5ee71 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
@@ -211,7 +211,7 @@
* Test that an outgoing connection to device
*/
@Test
- public void testConnect() {
+ public void testConnectAndDump() {
// Update the device policy so okToConnect() returns true
when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
when(mDatabaseManager
@@ -222,6 +222,9 @@
.getRemoteUuids(any(BluetoothDevice.class));
// Send a connect request
Assert.assertTrue("Connect expected to succeed", mService.connect(mDevice));
+
+ // Test dump() is not crashed.
+ mService.dump(new StringBuilder());
}
/**
diff --git a/android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientStateMachineTest.java b/android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientStateMachineTest.java
index 2a7a4f4..f1f88b8 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientStateMachineTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/bass_client/BassClientStateMachineTest.java
@@ -1490,6 +1490,11 @@
verify(mBassClientService, never()).sendBroadcast(any(Intent.class), anyString(), any());
}
+ @Test
+ public void dump_doesNotCrash() {
+ mBassClientStateMachine.dump(new StringBuilder());
+ }
+
private void initToDisconnectedState() {
allowConnection(true);
allowConnectGatt(true);
diff --git a/android/app/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceBinderTest.java b/android/app/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceBinderTest.java
index 0b1db1c..0e6d4d1 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceBinderTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceBinderTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2022 The Android Open Source Project
+ * 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.
@@ -54,7 +54,7 @@
mService.mAdapterProperties = mAdapterProperties;
doReturn(true).when(mService).isAvailable();
doReturn(mPackageManager).when(mService).getPackageManager();
- doReturn(new String[] { "com.android.bluetooth.btservice" })
+ doReturn(new String[] { "com.android.bluetooth.btservice.test" })
.when(mPackageManager).getPackagesForUid(anyInt());
mBinder = new AdapterService.AdapterServiceBinder(mService);
mAttributionSource = new AttributionSource.Builder(0).build();
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 721b457..29414ca 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
@@ -443,6 +443,7 @@
* Test: Turn Bluetooth on.
* Check whether the AdapterService gets started.
*/
+ @Ignore("b/228874625")
@Test
public void testEnable() {
Log.e("AdapterServiceTest", "testEnable() start");
diff --git a/android/app/tests/unit/src/com/android/bluetooth/csip/CsipSetCoordinatorServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/csip/CsipSetCoordinatorServiceTest.java
index 5b2baa2..4c7cc43 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/csip/CsipSetCoordinatorServiceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/csip/CsipSetCoordinatorServiceTest.java
@@ -532,6 +532,25 @@
group_id, intent.getIntExtra(BluetoothCsipSetCoordinator.EXTRA_CSIS_GROUP_ID, -1));
}
+ @Test
+ public void testDump_doesNotCrash() {
+ // Update the device policy so okToConnect() returns true
+ when(mDatabaseManager.getProfileConnectionPolicy(
+ mTestDevice, BluetoothProfile.CSIP_SET_COORDINATOR))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ doReturn(true).when(mCsipSetCoordinatorNativeInterface).connect(any(BluetoothDevice.class));
+ doReturn(true)
+ .when(mCsipSetCoordinatorNativeInterface)
+ .disconnect(any(BluetoothDevice.class));
+ doReturn(new ParcelUuid[] {BluetoothUuid.COORDINATED_SET})
+ .when(mAdapterService)
+ .getRemoteUuids(any(BluetoothDevice.class));
+ // add state machines for testing dump()
+ mService.connect(mTestDevice);
+
+ mService.dump(new StringBuilder());
+ }
+
/**
* Helper function to test ConnectionStateIntent() method
*/
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 90a1a56..a73d24c 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
@@ -1,8 +1,49 @@
+/*
+ * 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.gatt;
-import static org.mockito.Mockito.*;
+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.os.Binder;
+import android.os.ParcelUuid;
+import android.os.RemoteException;
+import android.os.WorkSource;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -23,18 +64,41 @@
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}.
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
public class GattServiceTest {
+
+ private static final String REMOTE_DEVICE_ADDRESS = "00:00:00:00:00:00";
+
private static final int TIMES_UP_AND_DOWN = 3;
private Context mTargetContext;
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 PeriodicScanManager mPeriodicScanManager;
+ @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 AdapterService mAdapterService;
@Before
@@ -44,9 +108,21 @@
MockitoAnnotations.initMocks(this);
TestUtils.setAdapterService(mAdapterService);
doReturn(true).when(mAdapterService).isStartedProfile(anyString());
+
+ mAdapter = BluetoothAdapter.getDefaultAdapter();
+ mAttributionSource = mAdapter.getAttributionSource();
+ mDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(REMOTE_DEVICE_ADDRESS);
+
TestUtils.startService(mServiceRule, GattService.class);
mService = GattService.getGattService();
Assert.assertNotNull(mService);
+
+ mService.mClientMap = mClientMap;
+ mService.mScannerMap = mScannerMap;
+ mService.mPeriodicScanManager = mPeriodicScanManager;
+ mService.mScanManager = mScanManager;
+ mService.mReliableQueue = mReliableQueue;
+ mService.mServerMap = mServerMap;
}
@After
@@ -63,7 +139,7 @@
@Test
public void testInitialize() {
- Assert.assertNotNull(GattService.getGattService());
+ Assert.assertEquals(mService, GattService.getGattService());
}
@Test
@@ -92,4 +168,523 @@
});
Assert.assertEquals(99700000000L, timestampNanos);
}
+
+ public void emptyClearServices() {
+ int serverIf = 1;
+
+ mService.clearServices(serverIf, mAttributionSource);
+ }
+
+ @Test
+ public void clientReadPhy() {
+ int clientIf = 1;
+ String address = REMOTE_DEVICE_ADDRESS;
+
+ Integer connId = 1;
+ doReturn(connId).when(mClientMap).connIdByAddress(clientIf, address);
+
+ mService.clientReadPhy(clientIf, address, mAttributionSource);
+ }
+
+ @Test
+ public void clientSetPreferredPhy() {
+ int clientIf = 1;
+ String address = REMOTE_DEVICE_ADDRESS;
+ int txPhy = 2;
+ int rxPhy = 1;
+ int phyOptions = 3;
+
+ Integer connId = 1;
+ doReturn(connId).when(mClientMap).connIdByAddress(clientIf, address);
+
+ mService.clientSetPreferredPhy(clientIf, address, txPhy, rxPhy, phyOptions,
+ mAttributionSource);
+ }
+
+ @Test
+ public void connectionParameterUpdate() {
+ int clientIf = 1;
+ String address = REMOTE_DEVICE_ADDRESS;
+
+ int connectionPriority = BluetoothGatt.CONNECTION_PRIORITY_HIGH;
+ mService.connectionParameterUpdate(clientIf, address, connectionPriority,
+ mAttributionSource);
+
+ connectionPriority = BluetoothGatt.CONNECTION_PRIORITY_LOW_POWER;
+ mService.connectionParameterUpdate(clientIf, address, connectionPriority,
+ mAttributionSource);
+
+ connectionPriority = BluetoothGatt.CONNECTION_PRIORITY_BALANCED;;
+ mService.connectionParameterUpdate(clientIf, address, connectionPriority,
+ mAttributionSource);
+ }
+
+ @Test
+ 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);
+ }
+
+ @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);
+ verify(mPeriodicScanManager).startSync(scanResult, skip, timeout, callback);
+ }
+
+ @Test
+ public void transferSync() {
+ int serviceData = 1;
+ int syncHandle = 2;
+
+ mService.transferSync(mDevice, serviceData, syncHandle, mAttributionSource);
+ verify(mPeriodicScanManager).transferSync(mDevice, serviceData, syncHandle);
+ }
+
+ @Test
+ public void transferSetInfo() {
+ int serviceData = 1;
+ int advHandle = 2;
+ IPeriodicAdvertisingCallback callback = mock(IPeriodicAdvertisingCallback.class);
+
+ mService.transferSetInfo(mDevice, serviceData, advHandle, callback,
+ mAttributionSource);
+ verify(mPeriodicScanManager).transferSetInfo(mDevice, serviceData, advHandle, callback);
+ }
+
+ @Test
+ public void unregisterSync() {
+ IPeriodicAdvertisingCallback callback = mock(IPeriodicAdvertisingCallback.class);
+
+ mService.unregisterSync(callback, mAttributionSource);
+ verify(mPeriodicScanManager).stopSync(callback);
+ }
+
+ @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/hap/HapClientTest.java b/android/app/tests/unit/src/com/android/bluetooth/hap/HapClientTest.java
index 1e82632..3c2819e 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/hap/HapClientTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/hap/HapClientTest.java
@@ -1052,6 +1052,24 @@
}
+ @Test
+ public void testDumpDoesNotCrash() {
+ // Update the device policy so okToConnect() returns true
+ when(mDatabaseManager
+ .getProfileConnectionPolicy(mDevice, BluetoothProfile.HAP_CLIENT))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ doReturn(true).when(mNativeInterface).connectHapClient(any(BluetoothDevice.class));
+ doReturn(true).when(mNativeInterface).disconnectHapClient(any(BluetoothDevice.class));
+
+ doReturn(new ParcelUuid[]{BluetoothUuid.HAS}).when(mAdapterService)
+ .getRemoteUuids(any(BluetoothDevice.class));
+
+ // Add state machine for testing dump()
+ mService.connect(mDevice);
+
+ mService.dump(new StringBuilder());
+ }
+
/**
* Helper function to test device connecting
*/
diff --git a/android/app/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidServiceTest.java
index cf74dfd..5478dd5 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidServiceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidServiceTest.java
@@ -1113,6 +1113,20 @@
verify(mNativeInterface).setVolume(0);
}
+ @Test
+ public void dump_doesNotCrash() {
+ // Update the device priority so okToConnect() returns true
+ when(mDatabaseManager
+ .getProfileConnectionPolicy(mSingleDevice, BluetoothProfile.HEARING_AID))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ doReturn(true).when(mNativeInterface).connectHearingAid(any(BluetoothDevice.class));
+
+ // Send a connect request
+ mService.connect(mSingleDevice);
+
+ mService.dump(new StringBuilder());
+ }
+
private void connectDevice(BluetoothDevice device) {
HearingAidStackEvent connCompletedEvent;
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 a511753..e85dc17 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
@@ -44,6 +44,7 @@
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;
@@ -100,6 +101,7 @@
Assert.assertNotNull(HeadsetClientService.getHeadsetClientService());
}
+ @Ignore("b/260202548")
@Test
public void testSendBIEVtoStateMachineWhenBatteryChanged() {
// Put mock state machine
@@ -136,4 +138,14 @@
eq(2),
anyInt());
}
+
+ @Test
+ public void testDumpDoesNotCrash() {
+ // Put mock state machine
+ BluetoothDevice device =
+ BluetoothAdapter.getDefaultAdapter().getRemoteDevice("00:01:02:03:04:05");
+ mService.getStateMachineMap().put(device, mStateMachine);
+
+ mService.dump(new StringBuilder());
+ }
}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/hfpclient/HfpNativeInterfaceTest.java b/android/app/tests/unit/src/com/android/bluetooth/hfpclient/HfpNativeInterfaceTest.java
index 7c5fd6b..771e476 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/hfpclient/HfpNativeInterfaceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/hfpclient/HfpNativeInterfaceTest.java
@@ -21,6 +21,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import com.android.bluetooth.TestUtils;
import com.android.bluetooth.btservice.AdapterService;
import org.junit.After;
@@ -45,14 +46,14 @@
MockitoAnnotations.initMocks(this);
when(mService.isAvailable()).thenReturn(true);
HeadsetClientService.setHeadsetClientService(mService);
- AdapterService.setAdapterService(mAdapterService);
+ TestUtils.setAdapterService(mAdapterService);
mNativeInterface = NativeInterface.getInstance();
}
@After
- public void tearDown() {
+ public void tearDown() throws Exception {
HeadsetClientService.setHeadsetClientService(null);
- AdapterService.setAdapterService(null);
+ TestUtils.clearAdapterService(mAdapterService);
}
@Test
diff --git a/android/app/tests/unit/src/com/android/bluetooth/hfpclient/connserv/HfpClientConnectionServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/hfpclient/connserv/HfpClientConnectionServiceTest.java
index 0692a68..7bf69c1 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/hfpclient/connserv/HfpClientConnectionServiceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/hfpclient/connserv/HfpClientConnectionServiceTest.java
@@ -89,11 +89,6 @@
Context targetContext = InstrumentationRegistry.getTargetContext();
- // HfpClientConnectionService is only enabled for some form factors, and the tests should
- // only be run if the service is enabled.
- assumeTrue(targetContext.getResources()
- .getBoolean(R.bool.hfp_client_connection_service_enabled));
-
// Setup a mock TelecomManager so our calls don't start a real instance of this service
doNothing().when(mMockTelecomManager).addNewIncomingCall(any(), any());
doNothing().when(mMockTelecomManager).addNewUnknownCall(any(), any());
diff --git a/android/app/tests/unit/src/com/android/bluetooth/hid/HidDeviceNativeInterfaceTest.java b/android/app/tests/unit/src/com/android/bluetooth/hid/HidDeviceNativeInterfaceTest.java
index 75e4f9c..fd70351 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/hid/HidDeviceNativeInterfaceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/hid/HidDeviceNativeInterfaceTest.java
@@ -24,6 +24,7 @@
import android.bluetooth.BluetoothHidDevice;
+import com.android.bluetooth.TestUtils;
import com.android.bluetooth.btservice.AdapterService;
import org.junit.After;
@@ -47,14 +48,14 @@
MockitoAnnotations.initMocks(this);
when(mService.isAvailable()).thenReturn(true);
HidDeviceService.setHidDeviceService(mService);
- AdapterService.setAdapterService(mAdapterService);
+ TestUtils.setAdapterService(mAdapterService);
mNativeInterface = HidDeviceNativeInterface.getInstance();
}
@After
- public void tearDown() {
+ public void tearDown() throws Exception {
HidDeviceService.setHidDeviceService(null);
- AdapterService.setAdapterService(null);
+ TestUtils.clearAdapterService(mAdapterService);
}
@Test
diff --git a/android/app/tests/unit/src/com/android/bluetooth/hid/HidHostServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/hid/HidHostServiceTest.java
index 4122c33..a9667d0 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/hid/HidHostServiceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/hid/HidHostServiceTest.java
@@ -133,6 +133,11 @@
badBondState, badPriorityValue, false);
}
+ @Test
+ public void testDumpDoesNotCrash() {
+ mService.dump(new StringBuilder());
+ }
+
/**
* Helper function to test okToConnect() method.
*
@@ -155,5 +160,4 @@
doReturn(true).when(mAdapterService).isQuietModeEnabled();
Assert.assertEquals(false, mService.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 683146a..d2c7cd1 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
@@ -68,6 +68,7 @@
import org.junit.After;
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;
@@ -1127,6 +1128,7 @@
* Test native interface audio configuration changed message handling
*/
@Test
+ @Ignore("b/258573934")
public void testMessageFromNativeAudioConfChangedActiveGroup() {
doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class));
connectTestDevice(mSingleDevice, testGroupId);
@@ -1312,6 +1314,7 @@
* Test native interface group status message handling
*/
@Test
+ @Ignore("b/258573934")
public void testLeadGroupDeviceDisconnects() {
int groupId = 1;
/* AUDIO_DIRECTION_OUTPUT_BIT = 0x01 */
@@ -1381,6 +1384,7 @@
* Test native interface group status message handling
*/
@Test
+ @Ignore("b/258573934")
public void testLeadGroupDeviceReconnects() {
int groupId = 1;
/* AUDIO_DIRECTION_OUTPUT_BIT = 0x01 */
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 1eb20d5..5661492 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.Telephony;
import android.provider.Telephony.Mms;
import android.provider.Telephony.Sms;
import android.telephony.TelephonyManager;
@@ -44,6 +47,8 @@
import com.android.bluetooth.mapapi.BluetoothMapContract;
import com.android.obex.ResponseCodes;
+import com.google.android.mms.pdu.PduHeaders;
+
import org.junit.After;
import org.junit.Assert;
import org.junit.Assume;
@@ -66,9 +71,10 @@
public class BluetoothMapContentObserverTest {
static final String TEST_NUMBER_ONE = "5551212";
static final String TEST_NUMBER_TWO = "5551234";
- static final int TEST_MAS_ID = 1;
- static final long TEST_HANDLE = 1;
- static final String TEST_URI_STR = "test_uri_str";
+ 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 = "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;
@@ -76,7 +82,34 @@
static final String TEST_ADDRESS = "test_address";
static final long TEST_DELETE_FOLDER_ID = BluetoothMapContract.FOLDER_ID_DELETED;
static final long TEST_INBOX_FOLDER_ID = BluetoothMapContract.FOLDER_ID_INBOX;
+ static final long TEST_SENT_FOLDER_ID = BluetoothMapContract.FOLDER_ID_SENT;
+ static final long TEST_DRAFT_FOLDER_ID = BluetoothMapContract.FOLDER_ID_DRAFT;
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 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 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;
@@ -90,6 +123,10 @@
private Context mContext;
@Mock
private ContentProviderClient mProviderClient;
+ @Mock
+ private BluetoothMapAccountItem mItem;
+ @Mock
+ private Intent mIntent;
@Spy
private BluetoothMethodProxy mMapMethodProxy = BluetoothMethodProxy.getInstance();
@@ -150,9 +187,12 @@
.thenReturn(Context.TELEPHONY_SERVICE);
when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserService);
when(mContext.getSystemServiceName(UserManager.class)).thenReturn(Context.USER_SERVICE);
- when(mInstance.getMasId()).thenReturn(TEST_MAS_ID);
+ when(mInstance.getMasId()).thenReturn(TEST_ID);
mObserver = new BluetoothMapContentObserver(mContext, mClient, mInstance, null, true);
+ mObserver.mProviderClient = mProviderClient;
+ mObserver.mAccount = mItem;
+ when(mItem.getType()).thenReturn(TYPE.IM);
}
@After
@@ -213,8 +253,8 @@
mObserver.setNotificationFilter(0);
String eventType = BluetoothMapContentObserver.EVENT_TYPE_NEW;
- BluetoothMapContentObserver.Event event = mObserver.new Event(eventType, TEST_HANDLE, null,
- null);
+ BluetoothMapContentObserver.Event event = mObserver.new Event(eventType, TEST_HANDLE_ONE,
+ null, null);
mObserver.sendEvent(event);
verify(mClient, never()).sendEvent(any(), anyInt());
@@ -264,12 +304,12 @@
when(mClient.isConnected()).thenReturn(true);
String eventType = BluetoothMapContentObserver.EVENT_TYPE_NEW;
- BluetoothMapContentObserver.Event event = mObserver.new Event(eventType, TEST_HANDLE, null,
- null);
+ BluetoothMapContentObserver.Event event = mObserver.new Event(eventType, TEST_HANDLE_ONE,
+ null, null);
mObserver.sendEvent(event);
- verify(mClient).sendEvent(event.encode(), TEST_MAS_ID);
+ verify(mClient).sendEvent(event.encode(), TEST_ID);
}
@Test
@@ -350,12 +390,12 @@
TYPE type = TYPE.SMS_GSM;
Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
BluetoothMapContentObserver.Msg msg = createSimpleMsg();
- map.put(TEST_HANDLE, msg);
+ map.put(TEST_HANDLE_ONE, msg);
mObserver.setMsgListSms(map, true);
doReturn(TEST_PLACEHOLDER_INT).when(mMapMethodProxy).contentResolverUpdate(any(), any(),
any(), any(), any());
- Assert.assertTrue(mObserver.setMessageStatusRead(TEST_HANDLE, type, TEST_URI_STR,
+ Assert.assertTrue(mObserver.setMessageStatusRead(TEST_HANDLE_ONE, type, TEST_URI_STR,
TEST_STATUS_VALUE));
Assert.assertEquals(msg.flagRead, TEST_STATUS_VALUE);
@@ -366,12 +406,12 @@
TYPE type = TYPE.MMS;
Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
BluetoothMapContentObserver.Msg msg = createSimpleMsg();
- map.put(TEST_HANDLE, msg);
+ map.put(TEST_HANDLE_ONE, msg);
mObserver.setMsgListMms(map, true);
doReturn(TEST_PLACEHOLDER_INT).when(mMapMethodProxy).contentResolverUpdate(any(), any(),
any(), any(), any());
- Assert.assertTrue(mObserver.setMessageStatusRead(TEST_HANDLE, type, TEST_URI_STR,
+ Assert.assertTrue(mObserver.setMessageStatusRead(TEST_HANDLE_ONE, type, TEST_URI_STR,
TEST_STATUS_VALUE));
Assert.assertEquals(msg.flagRead, TEST_STATUS_VALUE);
@@ -382,12 +422,12 @@
TYPE type = TYPE.EMAIL;
Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
BluetoothMapContentObserver.Msg msg = createSimpleMsg();
- map.put(TEST_HANDLE, msg);
+ map.put(TEST_HANDLE_ONE, msg);
mObserver.setMsgListMsg(map, true);
mObserver.mProviderClient = mProviderClient;
when(mProviderClient.update(any(), any(), any(), any())).thenReturn(TEST_PLACEHOLDER_INT);
- Assert.assertTrue(mObserver.setMessageStatusRead(TEST_HANDLE, type, TEST_URI_STR,
+ Assert.assertTrue(mObserver.setMessageStatusRead(TEST_HANDLE_ONE, type, TEST_URI_STR,
TEST_STATUS_VALUE));
Assert.assertEquals(msg.flagRead, TEST_STATUS_VALUE);
@@ -398,7 +438,7 @@
Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
BluetoothMapContentObserver.Msg msg = createMsgWithTypeAndThreadId(Mms.MESSAGE_BOX_ALL,
TEST_THREAD_ID);
- map.put(TEST_HANDLE, msg);
+ map.put(TEST_HANDLE_ONE, msg);
mObserver.setMsgListMms(map, true);
Assert.assertEquals(msg.threadId, TEST_THREAD_ID);
@@ -409,7 +449,7 @@
doReturn(TEST_PLACEHOLDER_INT).when(mMapMethodProxy).contentResolverUpdate(any(), any(),
any(), any(), any());
- Assert.assertTrue(mObserver.deleteMessageMms(TEST_HANDLE));
+ Assert.assertTrue(mObserver.deleteMessageMms(TEST_HANDLE_ONE));
Assert.assertEquals(msg.threadId, BluetoothMapContentObserver.DELETED_THREAD_ID);
}
@@ -419,9 +459,9 @@
Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
BluetoothMapContentObserver.Msg msg = createMsgWithTypeAndThreadId(Mms.MESSAGE_BOX_ALL,
TEST_THREAD_ID);
- map.put(TEST_HANDLE, msg);
+ map.put(TEST_HANDLE_ONE, msg);
mObserver.setMsgListMms(map, true);
- Assert.assertNotNull(mObserver.getMsgListMms().get(TEST_HANDLE));
+ Assert.assertNotNull(mObserver.getMsgListMms().get(TEST_HANDLE_ONE));
MatrixCursor cursor = new MatrixCursor(new String[] {Mms.THREAD_ID});
cursor.addRow(new Object[] {BluetoothMapContentObserver.DELETED_THREAD_ID});
@@ -430,9 +470,9 @@
doReturn(TEST_PLACEHOLDER_INT).when(mMapMethodProxy).contentResolverDelete(any(), any(),
any(), any());
- Assert.assertTrue(mObserver.deleteMessageMms(TEST_HANDLE));
+ Assert.assertTrue(mObserver.deleteMessageMms(TEST_HANDLE_ONE));
- Assert.assertNull(mObserver.getMsgListMms().get(TEST_HANDLE));
+ Assert.assertNull(mObserver.getMsgListMms().get(TEST_HANDLE_ONE));
}
@Test
@@ -440,7 +480,7 @@
Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
BluetoothMapContentObserver.Msg msg = createMsgWithTypeAndThreadId(Sms.MESSAGE_TYPE_ALL,
TEST_THREAD_ID);
- map.put(TEST_HANDLE, msg);
+ map.put(TEST_HANDLE_ONE, msg);
mObserver.setMsgListSms(map, true);
Assert.assertEquals(msg.threadId, TEST_THREAD_ID);
@@ -451,7 +491,7 @@
doReturn(TEST_PLACEHOLDER_INT).when(mMapMethodProxy).contentResolverUpdate(any(), any(),
any(), any(), any());
- Assert.assertTrue(mObserver.deleteMessageSms(TEST_HANDLE));
+ Assert.assertTrue(mObserver.deleteMessageSms(TEST_HANDLE_ONE));
Assert.assertEquals(msg.threadId, BluetoothMapContentObserver.DELETED_THREAD_ID);
}
@@ -461,9 +501,9 @@
Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
BluetoothMapContentObserver.Msg msg = createMsgWithTypeAndThreadId(Sms.MESSAGE_TYPE_ALL,
TEST_THREAD_ID);
- map.put(TEST_HANDLE, msg);
+ map.put(TEST_HANDLE_ONE, msg);
mObserver.setMsgListSms(map, true);
- Assert.assertNotNull(mObserver.getMsgListSms().get(TEST_HANDLE));
+ Assert.assertNotNull(mObserver.getMsgListSms().get(TEST_HANDLE_ONE));
MatrixCursor cursor = new MatrixCursor(new String[] {Mms.THREAD_ID});
cursor.addRow(new Object[] {BluetoothMapContentObserver.DELETED_THREAD_ID});
@@ -472,9 +512,9 @@
doReturn(TEST_PLACEHOLDER_INT).when(mMapMethodProxy).contentResolverDelete(any(), any(),
any(), any());
- Assert.assertTrue(mObserver.deleteMessageSms(TEST_HANDLE));
+ Assert.assertTrue(mObserver.deleteMessageSms(TEST_HANDLE_ONE));
- Assert.assertNull(mObserver.getMsgListSms().get(TEST_HANDLE));
+ Assert.assertNull(mObserver.getMsgListSms().get(TEST_HANDLE_ONE));
}
@Test
@@ -482,7 +522,7 @@
Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
BluetoothMapContentObserver.Msg msg = createMsgWithTypeAndThreadId(Mms.MESSAGE_BOX_ALL,
TEST_THREAD_ID);
- map.put(TEST_HANDLE, msg);
+ map.put(TEST_HANDLE_ONE, msg);
mObserver.setMsgListMms(map, true);
Assert.assertEquals(msg.threadId, TEST_THREAD_ID);
Assert.assertEquals(msg.type, Mms.MESSAGE_BOX_ALL);
@@ -498,7 +538,7 @@
doReturn(TEST_OLD_THREAD_ID).when(mMapMethodProxy).telephonyGetOrCreateThreadId(any(),
any());
- Assert.assertTrue(mObserver.unDeleteMessageMms(TEST_HANDLE));
+ Assert.assertTrue(mObserver.unDeleteMessageMms(TEST_HANDLE_ONE));
Assert.assertEquals(msg.threadId, TEST_OLD_THREAD_ID);
Assert.assertEquals(msg.type, Mms.MESSAGE_BOX_INBOX);
@@ -509,7 +549,7 @@
Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
BluetoothMapContentObserver.Msg msg = createMsgWithTypeAndThreadId(Mms.MESSAGE_BOX_ALL,
TEST_THREAD_ID);
- map.put(TEST_HANDLE, msg);
+ map.put(TEST_HANDLE_ONE, msg);
mObserver.setMsgListMms(map, true);
Assert.assertEquals(msg.threadId, TEST_THREAD_ID);
Assert.assertEquals(msg.type, Mms.MESSAGE_BOX_ALL);
@@ -525,7 +565,7 @@
doReturn(TEST_OLD_THREAD_ID).when(mMapMethodProxy).telephonyGetOrCreateThreadId(any(),
any());
- Assert.assertTrue(mObserver.unDeleteMessageMms(TEST_HANDLE));
+ Assert.assertTrue(mObserver.unDeleteMessageMms(TEST_HANDLE_ONE));
Assert.assertEquals(msg.threadId, TEST_OLD_THREAD_ID);
Assert.assertEquals(msg.type, Mms.MESSAGE_BOX_INBOX);
@@ -536,7 +576,7 @@
Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
BluetoothMapContentObserver.Msg msg = createMsgWithTypeAndThreadId(Mms.MESSAGE_BOX_ALL,
TEST_THREAD_ID);
- map.put(TEST_HANDLE, msg);
+ map.put(TEST_HANDLE_ONE, msg);
mObserver.setMsgListMms(map, true);
Assert.assertEquals(msg.threadId, TEST_THREAD_ID);
Assert.assertEquals(msg.type, Mms.MESSAGE_BOX_ALL);
@@ -549,7 +589,7 @@
doReturn(TEST_OLD_THREAD_ID).when(mMapMethodProxy).telephonyGetOrCreateThreadId(any(),
any());
- Assert.assertTrue(mObserver.unDeleteMessageMms(TEST_HANDLE));
+ Assert.assertTrue(mObserver.unDeleteMessageMms(TEST_HANDLE_ONE));
// Nothing changes when thread id is not BluetoothMapContentObserver.DELETED_THREAD_ID
Assert.assertEquals(msg.threadId, TEST_THREAD_ID);
@@ -561,7 +601,7 @@
Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
BluetoothMapContentObserver.Msg msg = createMsgWithTypeAndThreadId(Sms.MESSAGE_TYPE_ALL,
TEST_THREAD_ID);
- map.put(TEST_HANDLE, msg);
+ map.put(TEST_HANDLE_ONE, msg);
mObserver.setMsgListSms(map, true);
Assert.assertEquals(msg.threadId, TEST_THREAD_ID);
Assert.assertEquals(msg.type, Sms.MESSAGE_TYPE_ALL);
@@ -576,7 +616,7 @@
doReturn(TEST_OLD_THREAD_ID).when(mMapMethodProxy).telephonyGetOrCreateThreadId(any(),
any());
- Assert.assertTrue(mObserver.unDeleteMessageSms(TEST_HANDLE));
+ Assert.assertTrue(mObserver.unDeleteMessageSms(TEST_HANDLE_ONE));
Assert.assertEquals(msg.threadId, TEST_OLD_THREAD_ID);
Assert.assertEquals(msg.type, Sms.MESSAGE_TYPE_INBOX);
@@ -587,7 +627,7 @@
Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
BluetoothMapContentObserver.Msg msg = createMsgWithTypeAndThreadId(Sms.MESSAGE_TYPE_ALL,
TEST_THREAD_ID);
- map.put(TEST_HANDLE, msg);
+ map.put(TEST_HANDLE_ONE, msg);
mObserver.setMsgListSms(map, true);
Assert.assertEquals(msg.threadId, TEST_THREAD_ID);
Assert.assertEquals(msg.type, Sms.MESSAGE_TYPE_ALL);
@@ -600,7 +640,7 @@
doReturn(TEST_OLD_THREAD_ID).when(mMapMethodProxy).telephonyGetOrCreateThreadId(any(),
any());
- Assert.assertTrue(mObserver.unDeleteMessageSms(TEST_HANDLE));
+ Assert.assertTrue(mObserver.unDeleteMessageSms(TEST_HANDLE_ONE));
// Nothing changes when thread id is not BluetoothMapContentObserver.DELETED_THREAD_ID
Assert.assertEquals(msg.threadId, TEST_THREAD_ID);
@@ -633,13 +673,13 @@
Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
BluetoothMapContentObserver.Msg msg = createSimpleMsg();
- map.put(TEST_HANDLE, msg);
+ map.put(TEST_HANDLE_ONE, msg);
mObserver.setMsgListMsg(map, true);
doReturn(TEST_PLACEHOLDER_INT).when(mMapMethodProxy).contentResolverUpdate(any(), any(),
any(), any(), any());
Assert.assertTrue(mObserver.setEmailMessageStatusDelete(mCurrentFolder, TEST_URI_STR,
- TEST_HANDLE, BluetoothMapAppParams.STATUS_VALUE_YES));
+ TEST_HANDLE_ONE, BluetoothMapAppParams.STATUS_VALUE_YES));
Assert.assertEquals(msg.folderId, TEST_DELETE_FOLDER_ID);
}
@@ -651,13 +691,13 @@
Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
BluetoothMapContentObserver.Msg msg = createSimpleMsg();
- map.put(TEST_HANDLE, msg);
+ map.put(TEST_HANDLE_ONE, msg);
mObserver.setMsgListMsg(map, true);
doReturn(0).when(mMapMethodProxy).contentResolverUpdate(any(), any(),
any(), any(), any());
Assert.assertFalse(mObserver.setEmailMessageStatusDelete(mCurrentFolder, TEST_URI_STR,
- TEST_HANDLE, BluetoothMapAppParams.STATUS_VALUE_YES));
+ TEST_HANDLE_ONE, BluetoothMapAppParams.STATUS_VALUE_YES));
}
@Test
@@ -672,13 +712,13 @@
BluetoothMapContentObserver.Msg msg = createSimpleMsg();
msg.oldFolderId = TEST_OLD_FOLDER_ID;
msg.folderId = TEST_DELETE_FOLDER_ID;
- map.put(TEST_HANDLE, msg);
+ map.put(TEST_HANDLE_ONE, msg);
mObserver.setMsgListMsg(map, true);
doReturn(TEST_PLACEHOLDER_INT).when(mMapMethodProxy).contentResolverUpdate(any(), any(),
any(), any(), any());
Assert.assertTrue(mObserver.setEmailMessageStatusDelete(mCurrentFolder, TEST_URI_STR,
- TEST_HANDLE, BluetoothMapAppParams.STATUS_VALUE_NO));
+ TEST_HANDLE_ONE, BluetoothMapAppParams.STATUS_VALUE_NO));
Assert.assertEquals(msg.folderId, TEST_INBOX_FOLDER_ID);
}
@@ -695,13 +735,13 @@
BluetoothMapContentObserver.Msg msg = createSimpleMsg();
msg.oldFolderId = oldFolderId;
msg.folderId = TEST_DELETE_FOLDER_ID;
- map.put(TEST_HANDLE, msg);
+ map.put(TEST_HANDLE_ONE, msg);
mObserver.setMsgListMsg(map, true);
doReturn(TEST_PLACEHOLDER_INT).when(mMapMethodProxy).contentResolverUpdate(any(), any(),
any(), any(), any());
Assert.assertTrue(mObserver.setEmailMessageStatusDelete(mCurrentFolder, TEST_URI_STR,
- TEST_HANDLE, BluetoothMapAppParams.STATUS_VALUE_NO));
+ TEST_HANDLE_ONE, BluetoothMapAppParams.STATUS_VALUE_NO));
Assert.assertEquals(msg.folderId, TEST_INBOX_FOLDER_ID);
}
@@ -718,13 +758,13 @@
BluetoothMapContentObserver.Msg msg = createSimpleMsg();
msg.oldFolderId = TEST_OLD_FOLDER_ID;
msg.folderId = TEST_DELETE_FOLDER_ID;
- map.put(TEST_HANDLE, msg);
+ map.put(TEST_HANDLE_ONE, msg);
mObserver.setMsgListMsg(map, true);
doReturn(TEST_PLACEHOLDER_INT).when(mMapMethodProxy).contentResolverUpdate(any(), any(),
any(), any(), any());
Assert.assertTrue(mObserver.setEmailMessageStatusDelete(mCurrentFolder, TEST_URI_STR,
- TEST_HANDLE, BluetoothMapAppParams.STATUS_VALUE_NO));
+ TEST_HANDLE_ONE, BluetoothMapAppParams.STATUS_VALUE_NO));
Assert.assertEquals(msg.folderId, TEST_OLD_FOLDER_ID);
}
@@ -736,19 +776,19 @@
Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
BluetoothMapContentObserver.Msg msg = createSimpleMsg();
- map.put(TEST_HANDLE, msg);
+ map.put(TEST_HANDLE_ONE, msg);
mObserver.setMsgListMsg(map, true);
doReturn(TEST_PLACEHOLDER_INT).when(mMapMethodProxy).contentResolverUpdate(any(), any(),
any(), any(), any());
- Assert.assertTrue(mObserver.setMessageStatusDeleted(TEST_HANDLE, TYPE.EMAIL, mCurrentFolder,
- TEST_URI_STR, BluetoothMapAppParams.STATUS_VALUE_YES));
+ Assert.assertTrue(mObserver.setMessageStatusDeleted(TEST_HANDLE_ONE, TYPE.EMAIL,
+ mCurrentFolder, TEST_URI_STR, BluetoothMapAppParams.STATUS_VALUE_YES));
}
@Test
public void setMessageStatusDeleted_withTypeIm() {
- Assert.assertFalse(mObserver.setMessageStatusDeleted(TEST_HANDLE, TYPE.IM, mCurrentFolder,
- TEST_URI_STR, BluetoothMapAppParams.STATUS_VALUE_YES));
+ Assert.assertFalse(mObserver.setMessageStatusDeleted(TEST_HANDLE_ONE, TYPE.IM,
+ mCurrentFolder, TEST_URI_STR, BluetoothMapAppParams.STATUS_VALUE_YES));
}
@Test
@@ -760,9 +800,9 @@
// setMessageStatusDeleted with type Gsm or Mms calls either deleteMessage() or
// unDeleteMessage(), which returns false when no cursor is set with BluetoothMethodProxy.
- Assert.assertFalse(mObserver.setMessageStatusDeleted(TEST_HANDLE, TYPE.MMS, mCurrentFolder,
- TEST_URI_STR, BluetoothMapAppParams.STATUS_VALUE_NO));
- Assert.assertFalse(mObserver.setMessageStatusDeleted(TEST_HANDLE, TYPE.SMS_GSM,
+ Assert.assertFalse(mObserver.setMessageStatusDeleted(TEST_HANDLE_ONE, TYPE.MMS,
+ mCurrentFolder, TEST_URI_STR, BluetoothMapAppParams.STATUS_VALUE_NO));
+ Assert.assertFalse(mObserver.setMessageStatusDeleted(TEST_HANDLE_ONE, TYPE.SMS_GSM,
mCurrentFolder, TEST_URI_STR, BluetoothMapAppParams.STATUS_VALUE_NO));
}
@@ -775,25 +815,691 @@
// setMessageStatusDeleted with type Gsm or Mms calls either deleteMessage() or
// unDeleteMessage(), which returns false when no cursor is set with BluetoothMethodProxy.
- Assert.assertFalse(mObserver.setMessageStatusDeleted(TEST_HANDLE, TYPE.MMS, mCurrentFolder,
- TEST_URI_STR, BluetoothMapAppParams.STATUS_VALUE_YES));
- Assert.assertFalse(mObserver.setMessageStatusDeleted(TEST_HANDLE, TYPE.SMS_GSM,
+ Assert.assertFalse(mObserver.setMessageStatusDeleted(TEST_HANDLE_ONE, TYPE.MMS,
+ mCurrentFolder, TEST_URI_STR, BluetoothMapAppParams.STATUS_VALUE_YES));
+ Assert.assertFalse(mObserver.setMessageStatusDeleted(TEST_HANDLE_ONE, TYPE.SMS_GSM,
mCurrentFolder, TEST_URI_STR, BluetoothMapAppParams.STATUS_VALUE_YES));
}
@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;
+ 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(mMapMethodProxy).contentResolverQuery(any(), any(), any(), any(),
+ any(), any());
+
+ mObserver.mContactUri = mock(Uri.class);
+ when(mProviderClient.query(any(), any(), any(), any(), any())).thenReturn(cursor);
+
+ Map<String, BluetoothMapConvoContactElement> map = new HashMap<>();
+ mObserver.setContactList(map, true);
+ mObserver.initContactsList();
+ 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 handleMsgListChangesMsg_withNonExistingMessage_andVersion11() throws Exception {
+ MatrixCursor cursor = new MatrixCursor(new String[] {
+ BluetoothMapContract.MessageColumns._ID,
+ BluetoothMapContract.MessageColumns.FOLDER_ID,
+ BluetoothMapContract.MessageColumns.FLAG_READ,
+ BluetoothMapContract.MessageColumns.DATE,
+ BluetoothMapContract.MessageColumns.SUBJECT,
+ 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});
+ when(mProviderClient.query(any(), any(), any(), any(), any())).thenReturn(cursor);
+
+ Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
+ // Giving a different handle for msg below and cursor above makes handleMsgListChangesMsg()
+ // function for a non-existing message
+ BluetoothMapContentObserver.Msg msg = new BluetoothMapContentObserver.Msg(TEST_HANDLE_TWO,
+ TEST_INBOX_FOLDER_ID, TEST_READ_FLAG_ONE);
+ msg.localInitiatedSend = true;
+ msg.transparent = true;
+ map.put(TEST_HANDLE_TWO, msg);
+ mObserver.setMsgListMsg(map, true);
+ mObserver.mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V11;
+ mFolders.setFolderId(TEST_INBOX_FOLDER_ID);
+ mObserver.setFolderStructure(mFolders);
+
+ mObserver.handleMsgListChangesMsg(TEST_URI);
+
+ Assert.assertEquals(mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).id, TEST_HANDLE_ONE);
+ Assert.assertEquals(mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).type,
+ TEST_INBOX_FOLDER_ID);
+ Assert.assertEquals(mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).flagRead,
+ TEST_READ_FLAG_ONE);
+ }
+
+ @Test
+ public void handleMsgListChangesMsg_withNonExistingMessage_andVersion12() throws Exception {
+ MatrixCursor cursor = new MatrixCursor(new String[] {
+ BluetoothMapContract.MessageColumns._ID,
+ BluetoothMapContract.MessageColumns.FOLDER_ID,
+ BluetoothMapContract.MessageColumns.FLAG_READ,
+ BluetoothMapContract.MessageColumns.DATE,
+ BluetoothMapContract.MessageColumns.SUBJECT,
+ BluetoothMapContract.MessageColumns.FROM_LIST,
+ BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY,
+ 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"});
+ when(mProviderClient.query(any(), any(), any(), any(), any())).thenReturn(cursor);
+
+ Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
+ // Giving a different handle for msg below and cursor above makes handleMsgListChangesMsg()
+ // function for a non-existing message
+ BluetoothMapContentObserver.Msg msg = new BluetoothMapContentObserver.Msg(TEST_HANDLE_TWO,
+ TEST_INBOX_FOLDER_ID, TEST_READ_FLAG_ONE);
+ msg.localInitiatedSend = false;
+ msg.transparent = false;
+ map.put(TEST_HANDLE_TWO, msg);
+ mObserver.setMsgListMsg(map, true);
+ mObserver.mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V12;
+
+ mObserver.handleMsgListChangesMsg(TEST_URI);
+
+ Assert.assertEquals(mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).id, TEST_HANDLE_ONE);
+ Assert.assertEquals(mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).type,
+ TEST_INBOX_FOLDER_ID);
+ Assert.assertEquals(mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).flagRead,
+ TEST_READ_FLAG_ONE);
+ }
+
+ @Test
+ public void handleMsgListChangesMsg_withNonExistingMessage_andVersion10() throws Exception {
+ MatrixCursor cursor = new MatrixCursor(new String[] {
+ BluetoothMapContract.MessageColumns._ID,
+ BluetoothMapContract.MessageColumns.FOLDER_ID,
+ BluetoothMapContract.MessageColumns.FLAG_READ});
+ cursor.addRow(new Object[] {TEST_HANDLE_ONE, TEST_INBOX_FOLDER_ID, TEST_READ_FLAG_ONE});
+ when(mProviderClient.query(any(), any(), any(), any(), any())).thenReturn(cursor);
+
+ Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
+ // Giving a different handle for msg below and cursor above makes handleMsgListChangesMsg()
+ // function for a non-existing message
+ BluetoothMapContentObserver.Msg msg = new BluetoothMapContentObserver.Msg(TEST_HANDLE_TWO,
+ TEST_INBOX_FOLDER_ID, TEST_READ_FLAG_ONE);
+ msg.localInitiatedSend = false;
+ msg.transparent = false;
+ map.put(TEST_HANDLE_TWO, msg);
+ mObserver.setMsgListMsg(map, true);
+ mObserver.mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V10;
+ mFolders.setFolderId(TEST_HANDLE_TWO);
+ mObserver.setFolderStructure(mFolders);
+
+ mObserver.handleMsgListChangesMsg(TEST_URI);
+
+ Assert.assertEquals(mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).id, TEST_HANDLE_ONE);
+ Assert.assertEquals(mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).type,
+ TEST_INBOX_FOLDER_ID);
+ Assert.assertEquals(mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).flagRead,
+ TEST_READ_FLAG_ONE);
+ }
+
+ @Test
+ public void handleMsgListChangesMsg_withExistingMessage_andNonNullDeletedFolder()
+ throws Exception {
+ MatrixCursor cursor = new MatrixCursor(new String[] {
+ BluetoothMapContract.MessageColumns._ID,
+ BluetoothMapContract.MessageColumns.FOLDER_ID,
+ BluetoothMapContract.MessageColumns.FLAG_READ,});
+ cursor.addRow(new Object[] {TEST_HANDLE_ONE, TEST_DELETE_FOLDER_ID, TEST_READ_FLAG_ONE});
+ when(mProviderClient.query(any(), any(), any(), any(), any())).thenReturn(cursor);
+
+ Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
+ // Giving the same handle for msg below and cursor above makes handleMsgListChangesMsg()
+ // function for an existing message
+ BluetoothMapContentObserver.Msg msg = new BluetoothMapContentObserver.Msg(TEST_HANDLE_ONE,
+ TEST_INBOX_FOLDER_ID, TEST_READ_FLAG_ZERO);
+ map.put(TEST_HANDLE_ONE, msg);
+ mObserver.setMsgListMsg(map, true);
+ mObserver.mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V11;
+ setFolderStructureWithTelecomAndMsg(mFolders, BluetoothMapContract.FOLDER_NAME_DELETED,
+ TEST_DELETE_FOLDER_ID);
+ mObserver.setFolderStructure(mFolders);
+
+ mObserver.handleMsgListChangesMsg(TEST_URI);
+
+ Assert.assertEquals(mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).id, TEST_HANDLE_ONE);
+ Assert.assertEquals(mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).folderId,
+ TEST_DELETE_FOLDER_ID);
+ Assert.assertEquals(mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).flagRead,
+ TEST_READ_FLAG_ONE);
+ }
+
+ @Test
+ public void handleMsgListChangesMsg_withExistingMessage_andNonNullSentFolder()
+ throws Exception {
+ MatrixCursor cursor = new MatrixCursor(new String[] {
+ BluetoothMapContract.MessageColumns._ID,
+ BluetoothMapContract.MessageColumns.FOLDER_ID,
+ BluetoothMapContract.MessageColumns.FLAG_READ,});
+ cursor.addRow(new Object[] {TEST_HANDLE_ONE, TEST_SENT_FOLDER_ID, TEST_READ_FLAG_ONE});
+ when(mProviderClient.query(any(), any(), any(), any(), any())).thenReturn(cursor);
+
+ Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
+ // Giving the same handle for msg below and cursor above makes handleMsgListChangesMsg()
+ // function for an existing message
+ BluetoothMapContentObserver.Msg msg = new BluetoothMapContentObserver.Msg(TEST_HANDLE_ONE,
+ TEST_INBOX_FOLDER_ID, TEST_READ_FLAG_ZERO);
+ msg.localInitiatedSend = true;
+ msg.transparent = false;
+ map.put(TEST_HANDLE_ONE, msg);
+ mObserver.setMsgListMsg(map, true);
+ mObserver.mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V11;
+ setFolderStructureWithTelecomAndMsg(mFolders, BluetoothMapContract.FOLDER_NAME_SENT,
+ TEST_SENT_FOLDER_ID);
+ mObserver.setFolderStructure(mFolders);
+
+ mObserver.handleMsgListChangesMsg(TEST_URI);
+
+ Assert.assertEquals(mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).id, TEST_HANDLE_ONE);
+ Assert.assertEquals(mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).folderId,
+ TEST_SENT_FOLDER_ID);
+ Assert.assertEquals(mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).flagRead,
+ TEST_READ_FLAG_ONE);
+ }
+
+ @Test
+ public void handleMsgListChangesMsg_withExistingMessage_andNonNullTransparentSentFolder()
+ throws Exception {
+ MatrixCursor cursor = new MatrixCursor(new String[] {
+ BluetoothMapContract.MessageColumns._ID,
+ BluetoothMapContract.MessageColumns.FOLDER_ID,
+ BluetoothMapContract.MessageColumns.FLAG_READ,});
+ cursor.addRow(new Object[] {TEST_HANDLE_ONE, TEST_SENT_FOLDER_ID, TEST_READ_FLAG_ONE});
+ when(mProviderClient.query(any(), any(), any(), any(), any())).thenReturn(cursor);
+
+ Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
+ // Giving the same handle for msg below and cursor above makes handleMsgListChangesMsg()
+ // function for an existing message
+ BluetoothMapContentObserver.Msg msg = new BluetoothMapContentObserver.Msg(TEST_HANDLE_ONE,
+ TEST_INBOX_FOLDER_ID, TEST_READ_FLAG_ZERO);
+ msg.localInitiatedSend = true;
+ msg.transparent = true;
+ map.put(TEST_HANDLE_ONE, msg);
+ mObserver.setMsgListMsg(map, true);
+ mObserver.mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V11;
+ doReturn(TEST_PLACEHOLDER_INT).when(mMapMethodProxy).contentResolverDelete(any(), any(),
+ any(), any());
+ setFolderStructureWithTelecomAndMsg(mFolders, BluetoothMapContract.FOLDER_NAME_SENT,
+ TEST_SENT_FOLDER_ID);
+ mObserver.setFolderStructure(mFolders);
+ mObserver.mMessageUri = Mms.CONTENT_URI;
+
+ mObserver.handleMsgListChangesMsg(TEST_URI);
+
+ Assert.assertEquals(mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).id, TEST_HANDLE_ONE);
+ Assert.assertEquals(mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).folderId,
+ TEST_SENT_FOLDER_ID);
+ Assert.assertEquals(mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).flagRead,
+ TEST_READ_FLAG_ONE);
+ }
+
+ @Test
+ public void handleMsgListChangesMsg_withExistingMessage_andUnknownOldFolder()
+ throws Exception {
+ MatrixCursor cursor = new MatrixCursor(new String[] {
+ BluetoothMapContract.MessageColumns._ID,
+ BluetoothMapContract.MessageColumns.FOLDER_ID,
+ BluetoothMapContract.MessageColumns.FLAG_READ,});
+ cursor.addRow(new Object[] {TEST_HANDLE_ONE, TEST_INBOX_FOLDER_ID, TEST_READ_FLAG_ONE});
+ when(mProviderClient.query(any(), any(), any(), any(), any())).thenReturn(cursor);
+
+ Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
+ // Giving the same handle for msg below and cursor above makes handleMsgListChangesMsg()
+ // function for an existing message
+ BluetoothMapContentObserver.Msg msg = new BluetoothMapContentObserver.Msg(TEST_HANDLE_ONE,
+ TEST_SENT_FOLDER_ID, TEST_READ_FLAG_ZERO);
+ msg.localInitiatedSend = true;
+ msg.transparent = false;
+ map.put(TEST_HANDLE_ONE, msg);
+ mObserver.setMsgListMsg(map, true);
+ mObserver.mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V11;
+ setFolderStructureWithTelecomAndMsg(mFolders, BluetoothMapContract.FOLDER_NAME_DRAFT,
+ TEST_DRAFT_FOLDER_ID);
+ mObserver.setFolderStructure(mFolders);
+
+ mObserver.handleMsgListChangesMsg(TEST_URI);
+
+ Assert.assertEquals(mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).id, TEST_HANDLE_ONE);
+ Assert.assertEquals(mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).folderId,
+ TEST_INBOX_FOLDER_ID);
+ Assert.assertEquals(mObserver.getMsgListMsg().get(TEST_HANDLE_ONE).flagRead,
+ TEST_READ_FLAG_ONE);
+ }
+
+ @Test
+ 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});
+ 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,
+ 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_V11;
+
+ mObserver.handleMsgListChangesMms();
+
+ Assert.assertEquals(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).id, TEST_HANDLE_ONE);
+ Assert.assertEquals(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).type, TEST_MMS_TYPE_ALL);
+ Assert.assertEquals(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).threadId,
+ TEST_THREAD_ID);
+ Assert.assertEquals(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).flagRead,
+ TEST_READ_FLAG_ONE);
+ }
+
+ @Test
+ 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,
+ 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).id, TEST_HANDLE_ONE);
+ Assert.assertEquals(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).type, TEST_MMS_TYPE_ALL);
+ Assert.assertEquals(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).threadId,
+ TEST_THREAD_ID);
+ Assert.assertEquals(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).flagRead,
+ TEST_READ_FLAG_ONE);
+ }
+
+ @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,
+ 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 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_V10;
+
+ mObserver.handleMsgListChangesMms();
+
+ Assert.assertEquals(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).id, TEST_HANDLE_ONE);
+ Assert.assertEquals(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).type, TEST_MMS_TYPE_ALL);
+ Assert.assertEquals(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).threadId,
+ TEST_THREAD_ID);
+ Assert.assertEquals(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).flagRead,
+ TEST_READ_FLAG_ONE);
+ }
+
+ @Test
+ public void handleMsgListChangesMms_withExistingMessage_withNonEqualType_andLocalSendFalse() {
+ 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,
+ 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 handleMsgListChangesMms()
+ // function for an existing message
+ BluetoothMapContentObserver.Msg msg = new BluetoothMapContentObserver.Msg(TEST_HANDLE_ONE,
+ TEST_MMS_TYPE_INBOX, TEST_THREAD_ID, TEST_READ_FLAG_ZERO);
+ map.put(TEST_HANDLE_ONE, msg);
+ msg.localInitiatedSend = false;
+ mObserver.setMsgListMms(map, true);
+ mObserver.mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V12;
+
+ mObserver.handleMsgListChangesMms();
+
+ Assert.assertEquals(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).id, TEST_HANDLE_ONE);
+ Assert.assertEquals(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).type, TEST_MMS_TYPE_ALL);
+ Assert.assertEquals(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).threadId,
+ TEST_THREAD_ID);
+ Assert.assertEquals(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).flagRead,
+ TEST_READ_FLAG_ONE);
+ }
+
+ @Test
+ public void handleMsgListChangesMms_withExistingMessage_withNonEqualType_andLocalSendTrue() {
+ 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,
+ 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 handleMsgListChangesMms()
+ // function for an existing message
+ BluetoothMapContentObserver.Msg msg = new BluetoothMapContentObserver.Msg(TEST_HANDLE_ONE,
+ TEST_MMS_TYPE_INBOX, TEST_THREAD_ID, TEST_READ_FLAG_ZERO);
+ map.put(TEST_HANDLE_ONE, msg);
+ msg.localInitiatedSend = true;
+ mObserver.setMsgListMms(map, true);
+ mObserver.mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V12;
+
+ mObserver.handleMsgListChangesMms();
+
+ Assert.assertEquals(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).id, TEST_HANDLE_ONE);
+ Assert.assertEquals(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).type, TEST_MMS_TYPE_ALL);
+ Assert.assertEquals(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).threadId,
+ TEST_THREAD_ID);
+ Assert.assertEquals(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).flagRead,
+ TEST_READ_FLAG_ONE);
+ }
+
+ @Test
+ public void handleMsgListChangesMms_withExistingMessage_withDeletedThreadId() {
+ 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,
+ 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 handleMsgListChangesMms()
+ // function for an existing message
+ BluetoothMapContentObserver.Msg msg = new BluetoothMapContentObserver.Msg(TEST_HANDLE_ONE,
+ TEST_MMS_TYPE_ALL, TEST_THREAD_ID, TEST_READ_FLAG_ZERO);
+ map.put(TEST_HANDLE_ONE, msg);
+ msg.localInitiatedSend = true;
+ mObserver.setMsgListMms(map, true);
+ mObserver.mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V12;
+
+ mObserver.handleMsgListChangesMms();
+
+ Assert.assertEquals(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).id, TEST_HANDLE_ONE);
+ Assert.assertEquals(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).type, TEST_MMS_TYPE_ALL);
+ Assert.assertEquals(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).threadId,
+ BluetoothMapContentObserver.DELETED_THREAD_ID);
+ Assert.assertEquals(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).flagRead,
+ TEST_READ_FLAG_ONE);
+ }
+
+ @Test
+ public void handleMsgListChangesMms_withExistingMessage_withUndeletedThreadId() {
+ int undeletedThreadId = 0;
+ 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,
+ 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 handleMsgListChangesMms()
+ // function for an existing message
+ BluetoothMapContentObserver.Msg msg = new BluetoothMapContentObserver.Msg(TEST_HANDLE_ONE,
+ TEST_MMS_TYPE_ALL, TEST_THREAD_ID, TEST_READ_FLAG_ZERO);
+ map.put(TEST_HANDLE_ONE, msg);
+ msg.localInitiatedSend = true;
+ mObserver.setMsgListMms(map, true);
+ mObserver.mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V12;
+
+ mObserver.handleMsgListChangesMms();
+
+ Assert.assertEquals(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).id, TEST_HANDLE_ONE);
+ Assert.assertEquals(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).type, TEST_MMS_TYPE_ALL);
+ Assert.assertEquals(mObserver.getMsgListMms().get(TEST_HANDLE_ONE).threadId,
+ undeletedThreadId);
+ Assert.assertEquals(mObserver.getMsgListMms().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 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,
@@ -807,32 +1513,92 @@
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});
- doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(), any(), any(),
- any(), any());
-
- mObserver.mContactUri = mock(Uri.class);
- mObserver.mProviderClient = mProviderClient;
- when(mProviderClient.query(any(), any(), any(), any(), any())).thenReturn(cursor);
+ 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.initContactsList();
- BluetoothMapConvoContactElement contactElement = mObserver.getContactList().get(uci);
+ 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(), 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 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;
+
+ 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 42cb57f..6824582 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
@@ -18,18 +18,25 @@
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;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.database.MatrixCursor;
+import android.os.ParcelFileDescriptor;
+import android.provider.BaseColumns;
import android.provider.ContactsContract;
import android.provider.Telephony;
import android.provider.Telephony.Threads;
+import android.telephony.PhoneNumberUtils;
import android.telephony.TelephonyManager;
import android.text.util.Rfc822Tokenizer;
@@ -38,6 +45,8 @@
import com.android.bluetooth.BluetoothMethodProxy;
import com.android.bluetooth.SignedLongLong;
import com.android.bluetooth.map.BluetoothMapContent.FilterInfo;
+import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
+import com.android.bluetooth.mapapi.BluetoothMapContract;
import org.junit.After;
import org.junit.Before;
@@ -47,12 +56,37 @@
import org.mockito.MockitoAnnotations;
import org.mockito.Spy;
+import java.io.ByteArrayInputStream;
+import java.io.FileDescriptor;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.util.HashMap;
+
@RunWith(AndroidJUnit4.class)
public class BluetoothMapContentTest {
private static final String TEST_TEXT = "text";
private static final String TEST_TO_ADDRESS = "toName (toAddress) <to@google.com>";
private static final String TEST_CC_ADDRESS = "ccName (ccAddress) <cc@google.com>";
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_EMAIL = 2;
+ 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";
+ private static final String TEST_PHONE_NAME = "test_phone_name";
+ private static final long TEST_ID = 1;
+ private static final long TEST_INBOX_FOLDER_ID = BluetoothMapContract.FOLDER_ID_INBOX;
+ private static final long TEST_SENT_FOLDER_ID = BluetoothMapContract.FOLDER_ID_SENT;
+ private static final String TEST_SUBJECT = "subject";
+ private static final long TEST_DATE = 1;
+ private static final String TEST_MESSAGE_ID = "test_message_id";
+ 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";
@Mock
private BluetoothMapAccountItem mAccountItem;
@@ -71,7 +105,9 @@
private BluetoothMapContent mContent;
private FilterInfo mInfo;
- private BluetoothMapMessageListingElement mElement;
+ private BluetoothMapMessageListingElement mMessageListingElement;
+ private BluetoothMapConvoListingElement mConvoListingElement;
+ private BluetoothMapFolderElement mCurrentFolder;
@Before
public void setUp() {
@@ -80,7 +116,9 @@
mContent = new BluetoothMapContent(mContext, mAccountItem, mMasInstance);
mInfo = new FilterInfo();
- mElement = new BluetoothMapMessageListingElement();
+ mMessageListingElement = new BluetoothMapMessageListingElement();
+ mConvoListingElement = new BluetoothMapConvoListingElement();
+ mCurrentFolder = new BluetoothMapFolderElement("current", null);
}
@After
@@ -176,9 +214,9 @@
cursor.addRow(new Object[]{0, -1});
cursor.moveToFirst();
- mContent.setAttachment(mElement, cursor, mInfo, mParams);
+ mContent.setAttachment(mMessageListingElement, cursor, mInfo, mParams);
- assertThat(mElement.getAttachmentSize()).isEqualTo(1);
+ assertThat(mMessageListingElement.getAttachmentSize()).isEqualTo(1);
}
@Test
@@ -193,9 +231,9 @@
cursor.addRow(new Object[]{1, 0});
cursor.moveToFirst();
- mContent.setAttachment(mElement, cursor, mInfo, mParams);
+ mContent.setAttachment(mMessageListingElement, cursor, mInfo, mParams);
- assertThat(mElement.getAttachmentSize()).isEqualTo(1);
+ assertThat(mMessageListingElement.getAttachmentSize()).isEqualTo(1);
}
@Test
@@ -214,10 +252,10 @@
cursor.moveToFirst();
mContent.setRemoteFeatureMask(featureMask);
- mContent.setAttachment(mElement, cursor, mInfo, mParams);
+ mContent.setAttachment(mMessageListingElement, cursor, mInfo, mParams);
- assertThat(mElement.getAttachmentSize()).isEqualTo(1);
- assertThat(mElement.getAttachmentMimeTypes()).isEqualTo("test_mime_type");
+ assertThat(mMessageListingElement.getAttachmentSize()).isEqualTo(1);
+ assertThat(mMessageListingElement.getAttachmentMimeTypes()).isEqualTo("test_mime_type");
}
@Test
@@ -271,9 +309,9 @@
cursor.addRow(new Object[]{2L});
cursor.moveToFirst();
- mContent.setDateTime(mElement, cursor, mInfo, mParams);
+ mContent.setDateTime(mMessageListingElement, cursor, mInfo, mParams);
- assertThat(mElement.getDateTime()).isEqualTo(2L);
+ assertThat(mMessageListingElement.getDateTime()).isEqualTo(2L);
}
@Test
@@ -285,9 +323,9 @@
cursor.addRow(new Object[]{2L});
cursor.moveToFirst();
- mContent.setDateTime(mElement, cursor, mInfo, mParams);
+ mContent.setDateTime(mMessageListingElement, cursor, mInfo, mParams);
- assertThat(mElement.getDateTime()).isEqualTo(2L * 1000L);
+ assertThat(mMessageListingElement.getDateTime()).isEqualTo(2L * 1000L);
}
@Test
@@ -299,9 +337,9 @@
cursor.addRow(new Object[]{2L});
cursor.moveToFirst();
- mContent.setDateTime(mElement, cursor, mInfo, mParams);
+ mContent.setDateTime(mMessageListingElement, cursor, mInfo, mParams);
- assertThat(mElement.getDateTime()).isEqualTo(2L);
+ assertThat(mMessageListingElement.getDateTime()).isEqualTo(2L);
}
@Test
@@ -314,9 +352,9 @@
cursor.addRow(new Object[]{"test_delivery_status"});
cursor.moveToFirst();
- mContent.setDeliveryStatus(mElement, cursor, mInfo, mParams);
+ mContent.setDeliveryStatus(mMessageListingElement, cursor, mInfo, mParams);
- assertThat(mElement.getDeliveryStatus()).isEqualTo("test_delivery_status");
+ assertThat(mMessageListingElement.getDeliveryStatus()).isEqualTo("test_delivery_status");
}
@Test
@@ -341,8 +379,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();
}
@@ -388,8 +425,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();
}
@@ -442,4 +478,693 @@
assertThat(mContent.getRecipientAddressingEmail(cursor, mInfo)).isEqualTo(
expected.toString());
}
+
+ @Test
+ public void setRecipientAddressing_withFilterMsgTypeSms_andSmsMsgTypeInbox() {
+ when(mParams.getParameterMask()).thenReturn(
+ (long) BluetoothMapContent.MASK_RECIPIENT_ADDRESSING);
+ mInfo.mMsgType = FilterInfo.TYPE_SMS;
+ mInfo.mPhoneNum = TEST_ADDRESS;
+ mInfo.mSmsColType = 0;
+ MatrixCursor cursor = new MatrixCursor(new String[] {"SmsColType"});
+ cursor.addRow(new Object[] {Telephony.Sms.MESSAGE_TYPE_INBOX});
+ cursor.moveToFirst();
+
+ mContent.setRecipientAddressing(mMessageListingElement, cursor, mInfo, mParams);
+
+ assertThat(mMessageListingElement.getRecipientAddressing()).isEqualTo(TEST_ADDRESS);
+ }
+
+ @Test
+ public void setRecipientAddressing_withFilterMsgTypeSms_andSmsMsgTypeDraft() {
+ when(mParams.getParameterMask()).thenReturn(
+ (long) BluetoothMapContent.MASK_RECIPIENT_ADDRESSING);
+ mInfo.mMsgType = FilterInfo.TYPE_SMS;
+ mInfo.mSmsColType = 2;
+ MatrixCursor cursor = new MatrixCursor(new String[] {"RecipientIds",
+ Telephony.CanonicalAddressesColumns.ADDRESS, "SmsColType",
+ Telephony.Sms.ADDRESS, Telephony.Sms.THREAD_ID});
+ cursor.addRow(new Object[] {"recipientIdOne recipientIdTwo", "recipientAddress",
+ Telephony.Sms.MESSAGE_TYPE_DRAFT, null, "0"});
+ cursor.moveToFirst();
+
+ mContent.setRecipientAddressing(mMessageListingElement, cursor, mInfo, mParams);
+
+ assertThat(mMessageListingElement.getRecipientAddressing()).isEqualTo("recipientAddress");
+ }
+
+ @Test
+ public void setRecipientAddressing_withFilterMsgTypeMms() {
+ when(mParams.getParameterMask()).thenReturn(
+ (long) BluetoothMapContent.MASK_RECIPIENT_ADDRESSING);
+ mInfo.mMsgType = FilterInfo.TYPE_MMS;
+ MatrixCursor cursor = new MatrixCursor(
+ new String[]{BaseColumns._ID, Telephony.Mms.Addr.ADDRESS});
+ cursor.addRow(new Object[] {Telephony.Sms.MESSAGE_TYPE_INBOX, null});
+ cursor.moveToFirst();
+ doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(), any(), any(),
+ any(), any());
+
+ mContent.setRecipientAddressing(mMessageListingElement, cursor, mInfo, mParams);
+
+ assertThat(mMessageListingElement.getRecipientAddressing()).isEqualTo("");
+ }
+
+ @Test
+ public void setRecipientAddressing_withFilterMsgTypeEmail() {
+ when(mParams.getParameterMask()).thenReturn(
+ (long) BluetoothMapContent.MASK_RECIPIENT_ADDRESSING);
+ mInfo.mMsgType = FilterInfo.TYPE_EMAIL;
+ mInfo.mMessageColToAddress = 0;
+ mInfo.mMessageColCcAddress = 1;
+ mInfo.mMessageColBccAddress = 2;
+ MatrixCursor cursor = new MatrixCursor(
+ new String[]{"MessageColToAddress", "MessageColCcAddress", "MessageColBccAddress"});
+ cursor.addRow(new Object[]{TEST_TO_ADDRESS, TEST_CC_ADDRESS, TEST_BCC_ADDRESS});
+ cursor.moveToFirst();
+
+ mContent.setRecipientAddressing(mMessageListingElement, cursor, mInfo, mParams);
+
+ StringBuilder expected = new StringBuilder();
+ expected.append(Rfc822Tokenizer.tokenize(TEST_TO_ADDRESS)[0].getAddress());
+ expected.append("; ");
+ expected.append(Rfc822Tokenizer.tokenize(TEST_CC_ADDRESS)[0].getAddress());
+ expected.append("; ");
+ expected.append(Rfc822Tokenizer.tokenize(TEST_BCC_ADDRESS)[0].getAddress());
+ assertThat(mMessageListingElement.getRecipientAddressing()).isEqualTo(expected.toString());
+ }
+
+ @Test
+ public void setSenderAddressing_withFilterMsgTypeSms_andSmsMsgTypeInbox() {
+ when(mParams.getParameterMask()).thenReturn(
+ (long) BluetoothMapContent.MASK_SENDER_ADDRESSING);
+ mInfo.mMsgType = FilterInfo.TYPE_SMS;
+ mInfo.mSmsColType = 0;
+ mInfo.mSmsColAddress = 1;
+ MatrixCursor cursor = new MatrixCursor(new String[] {"SmsColType", "SmsColAddress"});
+ cursor.addRow(new Object[] {Telephony.Sms.MESSAGE_TYPE_INBOX, TEST_ADDRESS});
+ cursor.moveToFirst();
+ doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(), any(), any(),
+ any(), any());
+
+ mContent.setSenderAddressing(mMessageListingElement, cursor, mInfo, mParams);
+
+ assertThat(mMessageListingElement.getSenderAddressing()).isEqualTo(
+ PhoneNumberUtils.extractNetworkPortion(TEST_ADDRESS));
+ }
+
+ @Test
+ public void setSenderAddressing_withFilterMSgTypeSms_andSmsMsgTypeDraft() {
+ when(mParams.getParameterMask()).thenReturn(
+ (long) BluetoothMapContent.MASK_SENDER_ADDRESSING);
+ mInfo.mMsgType = FilterInfo.TYPE_SMS;
+ mInfo.mPhoneNum = null;
+ mInfo.mSmsColType = 0;
+ MatrixCursor cursor = new MatrixCursor(new String[] {"SmsColType"});
+ cursor.addRow(new Object[] {Telephony.Sms.MESSAGE_TYPE_DRAFT});
+ cursor.moveToFirst();
+
+ mContent.setSenderAddressing(mMessageListingElement, cursor, mInfo, mParams);
+
+ assertThat(mMessageListingElement.getSenderAddressing()).isEqualTo("");
+ }
+
+ @Test
+ public void setSenderAddressing_withFilterMsgTypeMms() {
+ when(mParams.getParameterMask()).thenReturn(
+ (long) BluetoothMapContent.MASK_SENDER_ADDRESSING);
+ mInfo.mMsgType = FilterInfo.TYPE_MMS;
+ mInfo.mMmsColId = 0;
+ MatrixCursor cursor = new MatrixCursor(
+ new String[]{"MmsColId", Telephony.Mms.Addr.ADDRESS});
+ cursor.addRow(new Object[] {0, ""});
+ cursor.moveToFirst();
+ doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(), any(), any(),
+ any(), any());
+
+ mContent.setSenderAddressing(mMessageListingElement, cursor, mInfo, mParams);
+
+ assertThat(mMessageListingElement.getSenderAddressing()).isEqualTo("");
+ }
+
+ @Test
+ public void setSenderAddressing_withFilterTypeEmail() {
+ when(mParams.getParameterMask()).thenReturn(
+ (long) BluetoothMapContent.MASK_SENDER_ADDRESSING);
+ mInfo.mMsgType = FilterInfo.TYPE_EMAIL;
+ mInfo.mMessageColFromAddress = 0;
+ MatrixCursor cursor = new MatrixCursor(
+ new String[]{"MessageColFromAddress"});
+ cursor.addRow(new Object[]{TEST_FROM_ADDRESS});
+ cursor.moveToFirst();
+
+ mContent.setSenderAddressing(mMessageListingElement, cursor, mInfo, mParams);
+
+ StringBuilder expected = new StringBuilder();
+ expected.append(Rfc822Tokenizer.tokenize(TEST_FROM_ADDRESS)[0].getAddress());
+ assertThat(mMessageListingElement.getSenderAddressing()).isEqualTo(expected.toString());
+ }
+
+ @Test
+ public void setSenderAddressing_withFilterTypeIm() {
+ when(mParams.getParameterMask()).thenReturn(
+ (long) BluetoothMapContent.MASK_SENDER_ADDRESSING);
+ mInfo.mMsgType = FilterInfo.TYPE_IM;
+ mInfo.mMessageColFromAddress = 0;
+ MatrixCursor cursor = new MatrixCursor(new String[] {"MessageColFromAddress",
+ BluetoothMapContract.ConvoContactColumns.UCI});
+ cursor.addRow(new Object[] {(long) 1, TEST_ADDRESS});
+ cursor.moveToFirst();
+ doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(), any(), any(),
+ any(), any());
+
+ mContent.setSenderAddressing(mMessageListingElement, cursor, mInfo, mParams);
+
+ assertThat(mMessageListingElement.getSenderAddressing()).isEqualTo(TEST_ADDRESS);
+ }
+
+ @Test
+ public void setSenderName_withFilterTypeSms_andSmsMsgTypeInbox() {
+ when(mParams.getParameterMask()).thenReturn((long) BluetoothMapContent.MASK_SENDER_NAME);
+ mInfo.mMsgType = FilterInfo.TYPE_SMS;
+ mInfo.mSmsColAddress = 1;
+ MatrixCursor cursor = new MatrixCursor(new String[] {Telephony.Sms.TYPE, "SmsColAddress",
+ ContactsContract.Contacts.DISPLAY_NAME});
+ cursor.addRow(new Object[] {Telephony.Sms.MESSAGE_TYPE_INBOX, TEST_PHONE, TEST_PHONE_NAME});
+ cursor.moveToFirst();
+ doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(), any(), any(),
+ any(), any());
+
+ mContent.setSenderName(mMessageListingElement, cursor, mInfo, mParams);
+
+ assertThat(mMessageListingElement.getSenderName()).isEqualTo(TEST_PHONE_NAME);
+ }
+
+ @Test
+ public void setSenderName_withFilterTypeSms_andSmsMsgTypeDraft() {
+ when(mParams.getParameterMask()).thenReturn((long) BluetoothMapContent.MASK_SENDER_NAME);
+ mInfo.mMsgType = FilterInfo.TYPE_SMS;
+ mInfo.mPhoneAlphaTag = TEST_NAME;
+ MatrixCursor cursor = new MatrixCursor(new String[] {Telephony.Sms.TYPE});
+ cursor.addRow(new Object[] {Telephony.Sms.MESSAGE_TYPE_DRAFT});
+ cursor.moveToFirst();
+ doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(), any(), any(),
+ any(), any());
+
+ mContent.setSenderName(mMessageListingElement, cursor, mInfo, mParams);
+
+ assertThat(mMessageListingElement.getSenderName()).isEqualTo(TEST_NAME);
+ }
+
+ @Test
+ public void setSenderName_withFilterTypeMms_withNonNullSenderAddressing() {
+ when(mParams.getParameterMask()).thenReturn((long) BluetoothMapContent.MASK_SENDER_NAME);
+ mInfo.mMsgType = FilterInfo.TYPE_MMS;
+ mInfo.mMmsColId = 0;
+ mMessageListingElement.setSenderAddressing(TEST_ADDRESS);
+ MatrixCursor cursor = new MatrixCursor(new String[] {"MmsColId", Telephony.Mms.Addr.ADDRESS,
+ ContactsContract.Contacts.DISPLAY_NAME});
+ cursor.addRow(new Object[] {0, TEST_PHONE, TEST_PHONE_NAME});
+ cursor.moveToFirst();
+ doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(), any(), any(),
+ any(), any());
+
+ mContent.setSenderName(mMessageListingElement, cursor, mInfo, mParams);
+
+ assertThat(mMessageListingElement.getSenderName()).isEqualTo(TEST_PHONE_NAME);
+ }
+
+ @Test
+ public void setSenderName_withFilterTypeMms_withNullSenderAddressing() {
+ when(mParams.getParameterMask()).thenReturn((long) BluetoothMapContent.MASK_SENDER_NAME);
+ mInfo.mMsgType = FilterInfo.TYPE_MMS;
+ mInfo.mMmsColId = 0;
+ MatrixCursor cursor = new MatrixCursor(new String[] {"MmsColId"});
+ cursor.addRow(new Object[] {0});
+ cursor.moveToFirst();
+ doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(), any(), any(),
+ any(), any());
+
+ mContent.setSenderName(mMessageListingElement, cursor, mInfo, mParams);
+
+ assertThat(mMessageListingElement.getSenderName()).isEqualTo("");
+ }
+
+ @Test
+ public void setSenderName_withFilterTypeEmail() {
+ when(mParams.getParameterMask()).thenReturn((long) BluetoothMapContent.MASK_SENDER_NAME);
+ mInfo.mMsgType = FilterInfo.TYPE_EMAIL;
+ mInfo.mMessageColFromAddress = 0;
+ MatrixCursor cursor = new MatrixCursor(new String[] {"MessageColFromAddress"});
+ cursor.addRow(new Object[] {TEST_FROM_ADDRESS});
+ cursor.moveToFirst();
+
+ mContent.setSenderName(mMessageListingElement, cursor, mInfo, mParams);
+
+ StringBuilder expected = new StringBuilder();
+ expected.append(Rfc822Tokenizer.tokenize(TEST_FROM_ADDRESS)[0].getName());
+ assertThat(mMessageListingElement.getSenderName()).isEqualTo(expected.toString());
+ }
+
+ @Test
+ public void setSenderName_withFilterTypeIm() {
+ when(mParams.getParameterMask()).thenReturn((long) BluetoothMapContent.MASK_SENDER_NAME);
+ mInfo.mMsgType = FilterInfo.TYPE_IM;
+ mInfo.mMessageColFromAddress = 0;
+ MatrixCursor cursor = new MatrixCursor(new String[] {"MessageColFromAddress",
+ BluetoothMapContract.ConvoContactColumns.NAME});
+ cursor.addRow(new Object[] {(long) 1, TEST_NAME});
+ cursor.moveToFirst();
+ doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(), any(), any(),
+ any(), any());
+
+ mContent.setSenderName(mMessageListingElement, cursor, mInfo, mParams);
+
+ assertThat(mMessageListingElement.getSenderName()).isEqualTo(TEST_NAME);
+ }
+
+ @Test
+ public void setters_withConvoList() {
+ BluetoothMapMasInstance instance = spy(BluetoothMapMasInstance.class);
+ BluetoothMapContent content = new BluetoothMapContent(mContext, mAccountItem, instance);
+ HashMap<Long, BluetoothMapConvoListingElement> emailMap =
+ new HashMap<Long, BluetoothMapConvoListingElement>();
+ HashMap<Long, BluetoothMapConvoListingElement> smsMap =
+ new HashMap<Long, BluetoothMapConvoListingElement>();
+
+ content.setImEmailConvoList(emailMap);
+ content.setSmsMmsConvoList(smsMap);
+
+ assertThat(content.getImEmailConvoList()).isEqualTo(emailMap);
+ assertThat(content.getSmsMmsConvoList()).isEqualTo(smsMap);
+ }
+
+ @Test
+ public void setLastActivity_withFilterTypeSms() {
+ mInfo.mMsgType = FilterInfo.TYPE_SMS;
+ mInfo.mConvoColLastActivity = 0;
+ MatrixCursor cursor = new MatrixCursor(new String[] {"ConvoColLastActivity",
+ "MmsSmsThreadColDate"});
+ cursor.addRow(new Object[] {TEST_DATE_EMAIL, TEST_DATE_SMS});
+ cursor.moveToFirst();
+
+ mContent.setLastActivity(mConvoListingElement, cursor, mInfo);
+
+ assertThat(mConvoListingElement.getLastActivity()).isEqualTo(TEST_DATE_SMS);
+ }
+
+ @Test
+ public void setLastActivity_withFilterTypeEmail() {
+ mInfo.mMsgType = FilterInfo.TYPE_EMAIL;
+ mInfo.mConvoColLastActivity = 0;
+ MatrixCursor cursor = new MatrixCursor(new String[] {"ConvoColLastActivity",
+ "MmsSmsThreadColDate"});
+ cursor.addRow(new Object[] {TEST_DATE_EMAIL, TEST_DATE_SMS});
+ cursor.moveToFirst();
+
+ mContent.setLastActivity(mConvoListingElement, cursor, mInfo);
+
+ assertThat(mConvoListingElement.getLastActivity()).isEqualTo(TEST_DATE_EMAIL);
+ }
+
+ @Test
+ public void getEmailMessage_withCharsetNative() {
+ when(mParams.getCharset()).thenReturn(BluetoothMapContent.MAP_MESSAGE_CHARSET_NATIVE);
+
+ assertThrows(IllegalArgumentException.class, () -> mContent.getEmailMessage(TEST_ID,
+ mParams, mCurrentFolder));
+ }
+
+ @Test
+ public void getEmailMessage_withEmptyCursor() {
+ when(mParams.getCharset()).thenReturn(BluetoothMapContent.MAP_MESSAGE_CHARSET_UTF8);
+ MatrixCursor cursor = new MatrixCursor(new String[] {});
+ doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(), any(), any(),
+ any(), any());
+
+ assertThrows(IllegalArgumentException.class, () -> mContent.getEmailMessage(TEST_ID,
+ mParams, mCurrentFolder));
+ }
+
+ @Test
+ public void getEmailMessage_withFileNotFoundExceptionForEmailBodyAccess() throws Exception {
+ when(mParams.getCharset()).thenReturn(BluetoothMapContent.MAP_MESSAGE_CHARSET_UTF8);
+ when(mParams.getFractionRequest()).thenReturn(BluetoothMapAppParams.FRACTION_REQUEST_FIRST);
+ when(mParams.getAttachment()).thenReturn(0);
+
+ MatrixCursor cursor = new MatrixCursor(new String[] {
+ BluetoothMapContract.MessageColumns.RECEPTION_STATE,
+ BluetoothMapContract.MessageColumns.FLAG_READ,
+ BluetoothMapContract.MessageColumns.FOLDER_ID,
+ BluetoothMapContract.MessageColumns.TO_LIST,
+ BluetoothMapContract.MessageColumns.FROM_LIST
+ });
+ cursor.addRow(new Object[] {BluetoothMapContract.RECEPTION_STATE_FRACTIONED, "1",
+ TEST_INBOX_FOLDER_ID, TEST_TO_ADDRESS, TEST_FROM_ADDRESS});
+ cursor.moveToFirst();
+ doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(), any(), any(),
+ any(), any());
+
+ mCurrentFolder.setFolderId(TEST_INBOX_FOLDER_ID);
+ // This mock sets up FileNotFoundException during email body access
+ doThrow(FileNotFoundException.class).when(
+ mMapMethodProxy).contentResolverOpenFileDescriptor(any(), any(), any());
+
+ byte[] encodedMessageEmail = mContent.getEmailMessage(TEST_ID, mParams, mCurrentFolder);
+ InputStream inputStream = new ByteArrayInputStream(encodedMessageEmail);
+ BluetoothMapbMessage messageParsed = BluetoothMapbMessage.parse(inputStream,
+ BluetoothMapAppParams.CHARSET_UTF8);
+
+ assertThat(messageParsed.getType()).isEqualTo(TYPE.EMAIL);
+ assertThat(messageParsed.getVersionString()).isEqualTo("VERSION:" +
+ mContent.mMessageVersion);
+ assertThat(messageParsed.getFolder()).isEqualTo(mCurrentFolder.getFullPath());
+ assertThat(messageParsed.getRecipients().get(0).getName()).isEqualTo(
+ Rfc822Tokenizer.tokenize(TEST_TO_ADDRESS)[0].getName());
+ assertThat(messageParsed.getRecipients().get(0).getFirstEmail()).isEqualTo(
+ Rfc822Tokenizer.tokenize(TEST_TO_ADDRESS)[0].getAddress());
+ assertThat(messageParsed.getOriginators().get(0).getName()).isEqualTo(
+ Rfc822Tokenizer.tokenize(TEST_FROM_ADDRESS)[0].getName());
+ assertThat(messageParsed.getOriginators().get(0).getFirstEmail()).isEqualTo(
+ Rfc822Tokenizer.tokenize(TEST_FROM_ADDRESS)[0].getAddress());
+ }
+
+ @Test
+ public void getEmailMessage_withNullPointerExceptionForEmailBodyAccess() throws Exception {
+ when(mParams.getCharset()).thenReturn(BluetoothMapContent.MAP_MESSAGE_CHARSET_UTF8);
+ when(mParams.getFractionRequest()).thenReturn(BluetoothMapAppParams.FRACTION_REQUEST_FIRST);
+ when(mParams.getAttachment()).thenReturn(0);
+
+ MatrixCursor cursor = new MatrixCursor(new String[] {
+ BluetoothMapContract.MessageColumns.RECEPTION_STATE,
+ BluetoothMapContract.MessageColumns.FLAG_READ,
+ BluetoothMapContract.MessageColumns.FOLDER_ID,
+ BluetoothMapContract.MessageColumns.TO_LIST,
+ BluetoothMapContract.MessageColumns.FROM_LIST
+ });
+ cursor.addRow(new Object[] {BluetoothMapContract.RECEPTION_STATE_FRACTIONED, null,
+ TEST_INBOX_FOLDER_ID, TEST_TO_ADDRESS, TEST_FROM_ADDRESS});
+ cursor.moveToFirst();
+ doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(), any(), any(),
+ any(), any());
+
+ mCurrentFolder.setFolderId(TEST_INBOX_FOLDER_ID);
+ // This mock sets up NullPointerException during email body access
+ doThrow(NullPointerException.class).when(
+ mMapMethodProxy).contentResolverOpenFileDescriptor(any(), any(), any());
+
+ byte[] encodedMessageEmail = mContent.getEmailMessage(TEST_ID, mParams, mCurrentFolder);
+ InputStream inputStream = new ByteArrayInputStream(encodedMessageEmail);
+ BluetoothMapbMessage messageParsed = BluetoothMapbMessage.parse(inputStream,
+ BluetoothMapAppParams.CHARSET_UTF8);
+
+ assertThat(messageParsed.getType()).isEqualTo(TYPE.EMAIL);
+ assertThat(messageParsed.getVersionString()).isEqualTo("VERSION:" +
+ mContent.mMessageVersion);
+ assertThat(messageParsed.getFolder()).isEqualTo(mCurrentFolder.getFullPath());
+ assertThat(messageParsed.getRecipients().get(0).getName()).isEqualTo(
+ Rfc822Tokenizer.tokenize(TEST_TO_ADDRESS)[0].getName());
+ assertThat(messageParsed.getRecipients().get(0).getFirstEmail()).isEqualTo(
+ Rfc822Tokenizer.tokenize(TEST_TO_ADDRESS)[0].getAddress());
+ assertThat(messageParsed.getOriginators().get(0).getName()).isEqualTo(
+ Rfc822Tokenizer.tokenize(TEST_FROM_ADDRESS)[0].getName());
+ assertThat(messageParsed.getOriginators().get(0).getFirstEmail()).isEqualTo(
+ Rfc822Tokenizer.tokenize(TEST_FROM_ADDRESS)[0].getAddress());
+ }
+
+ @Test
+ public void getEmailMessage() throws Exception {
+ when(mParams.getCharset()).thenReturn(BluetoothMapContent.MAP_MESSAGE_CHARSET_UTF8);
+ when(mParams.getFractionRequest()).thenReturn(BluetoothMapAppParams.FRACTION_REQUEST_FIRST);
+ when(mParams.getAttachment()).thenReturn(0);
+
+ MatrixCursor cursor = new MatrixCursor(new String[] {
+ BluetoothMapContract.MessageColumns.RECEPTION_STATE,
+ BluetoothMapContract.MessageColumns.FLAG_READ,
+ BluetoothMapContract.MessageColumns.FOLDER_ID,
+ BluetoothMapContract.MessageColumns.TO_LIST,
+ BluetoothMapContract.MessageColumns.FROM_LIST
+ });
+ cursor.addRow(new Object[] {BluetoothMapContract.RECEPTION_STATE_FRACTIONED, "1",
+ TEST_INBOX_FOLDER_ID, TEST_TO_ADDRESS, TEST_FROM_ADDRESS});
+ cursor.moveToFirst();
+ doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(), any(), any(),
+ any(), any());
+
+ mCurrentFolder.setFolderId(TEST_INBOX_FOLDER_ID);
+ FileDescriptor fd = new FileDescriptor();
+ ParcelFileDescriptor pfd = mock(ParcelFileDescriptor.class);
+ doReturn(fd).when(pfd).getFileDescriptor();
+ doReturn(pfd).when(mMapMethodProxy).contentResolverOpenFileDescriptor(any(), any(), any());
+
+ byte[] encodedMessageEmail = mContent.getEmailMessage(TEST_ID, mParams, mCurrentFolder);
+ InputStream inputStream = new ByteArrayInputStream(encodedMessageEmail);
+ BluetoothMapbMessage messageParsed = BluetoothMapbMessage.parse(inputStream,
+ BluetoothMapAppParams.CHARSET_UTF8);
+
+ assertThat(messageParsed.getType()).isEqualTo(TYPE.EMAIL);
+ assertThat(messageParsed.getVersionString()).isEqualTo("VERSION:" +
+ mContent.mMessageVersion);
+ assertThat(messageParsed.getFolder()).isEqualTo(mCurrentFolder.getFullPath());
+ assertThat(messageParsed.getRecipients().get(0).getName()).isEqualTo(
+ Rfc822Tokenizer.tokenize(TEST_TO_ADDRESS)[0].getName());
+ assertThat(messageParsed.getRecipients().get(0).getFirstEmail()).isEqualTo(
+ Rfc822Tokenizer.tokenize(TEST_TO_ADDRESS)[0].getAddress());
+ assertThat(messageParsed.getOriginators().get(0).getName()).isEqualTo(
+ Rfc822Tokenizer.tokenize(TEST_FROM_ADDRESS)[0].getName());
+ assertThat(messageParsed.getOriginators().get(0).getFirstEmail()).isEqualTo(
+ Rfc822Tokenizer.tokenize(TEST_FROM_ADDRESS)[0].getAddress());
+ }
+
+ @Test
+ public void getIMMessage_withCharsetNative() {
+ when(mParams.getCharset()).thenReturn(BluetoothMapContent.MAP_MESSAGE_CHARSET_NATIVE);
+
+ assertThrows(IllegalArgumentException.class, () -> mContent.getIMMessage(TEST_ID,
+ mParams, mCurrentFolder));
+ }
+
+ @Test
+ public void getIMMessage_withEmptyCursor() {
+ when(mParams.getCharset()).thenReturn(BluetoothMapContent.MAP_MESSAGE_CHARSET_UTF8);
+ MatrixCursor cursor = new MatrixCursor(new String[] {});
+ cursor.moveToFirst();
+ doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(), any(), any(),
+ any(), any());
+
+ assertThrows(IllegalArgumentException.class, () -> mContent.getIMMessage(TEST_ID,
+ mParams, mCurrentFolder));
+ }
+
+ @Test
+ public void getIMMessage_withSentFolderId() throws Exception {
+ when(mParams.getCharset()).thenReturn(BluetoothMapContent.MAP_MESSAGE_CHARSET_UTF8);
+ when(mParams.getAttachment()).thenReturn(1);
+
+ MatrixCursor cursor = new MatrixCursor(new String[] {
+ BluetoothMapContract.MessageColumns.FLAG_READ,
+ BluetoothMapContract.MessageColumns.THREAD_ID,
+ BluetoothMapContract.MessageColumns.FOLDER_ID,
+ BluetoothMapContract.MessageColumns.SUBJECT,
+ BluetoothMapContract.MessageColumns._ID,
+ BluetoothMapContract.MessageColumns.DATE,
+ BluetoothMapContract.MessageColumns.ATTACHMENT_SIZE,
+ BluetoothMapContract.MessageColumns.BODY,
+ BluetoothMapContract.ConvoContactColumns.NAME,
+ BluetoothMapContract.ConvoContactColumns.X_BT_UID,
+ BluetoothMapContract.ConvoContactColumns.NICKNAME,
+ BluetoothMapContract.ConvoContactColumns.UCI,
+ });
+ cursor.addRow(new Object[] {1, 1, TEST_SENT_FOLDER_ID, TEST_SUBJECT, TEST_MESSAGE_ID,
+ TEST_DATE, 0, "body", TEST_NAME, TEST_FIRST_BT_UID, TEST_FORMATTED_NAME,
+ TEST_FIRST_BT_UCI_RECIPIENT});
+ cursor.moveToFirst();
+ doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(), any(), any(),
+ any(), any());
+
+ mCurrentFolder.setFolderId(TEST_SENT_FOLDER_ID);
+ when(mAccountItem.getUciFull()).thenReturn(TEST_FIRST_BT_UCI_ORIGINATOR);
+
+ byte[] encodedMessageMime = mContent.getIMMessage(TEST_ID, mParams, mCurrentFolder);
+ InputStream inputStream = new ByteArrayInputStream(encodedMessageMime);
+ BluetoothMapbMessage messageMimeParsed = BluetoothMapbMessage.parse(inputStream, 1);
+
+ assertThat(messageMimeParsed.mAppParamCharset).isEqualTo(1);
+ assertThat(messageMimeParsed.getType()).isEqualTo(TYPE.IM);
+ assertThat(messageMimeParsed.getVersionString()).isEqualTo("VERSION:" +
+ mContent.mMessageVersion);
+ assertThat(messageMimeParsed.getFolder()).isEqualTo(mCurrentFolder.getFullPath());
+ assertThat(messageMimeParsed.getRecipients().size()).isEqualTo(1);
+ assertThat(messageMimeParsed.getOriginators().size()).isEqualTo(1);
+ assertThat(messageMimeParsed.getOriginators().get(0).getName()).isEmpty();
+ assertThat(messageMimeParsed.getRecipients().get(0).getName()).isEqualTo(
+ TEST_FORMATTED_NAME);
+ }
+
+ @Test
+ public void getIMMessage_withInboxFolderId() throws Exception {
+ when(mParams.getCharset()).thenReturn(BluetoothMapContent.MAP_MESSAGE_CHARSET_UTF8);
+ when(mParams.getAttachment()).thenReturn(1);
+
+ MatrixCursor cursor = new MatrixCursor(new String[] {
+ BluetoothMapContract.MessageColumns.FLAG_READ,
+ BluetoothMapContract.MessageColumns.THREAD_ID,
+ BluetoothMapContract.MessageColumns.FOLDER_ID,
+ BluetoothMapContract.MessageColumns.SUBJECT,
+ BluetoothMapContract.MessageColumns._ID,
+ BluetoothMapContract.MessageColumns.DATE,
+ BluetoothMapContract.MessageColumns.ATTACHMENT_SIZE,
+ BluetoothMapContract.MessageColumns.BODY,
+ BluetoothMapContract.ConvoContactColumns.NAME,
+ BluetoothMapContract.ConvoContactColumns.X_BT_UID,
+ BluetoothMapContract.ConvoContactColumns.NICKNAME,
+ BluetoothMapContract.ConvoContactColumns.UCI,
+ });
+ cursor.addRow(new Object[] {0, 1, TEST_INBOX_FOLDER_ID, TEST_SUBJECT, TEST_MESSAGE_ID,
+ TEST_DATE, 0, "body", TEST_NAME, TEST_FIRST_BT_UID, TEST_FORMATTED_NAME,
+ TEST_FIRST_BT_UCI_ORIGINATOR});
+ cursor.moveToFirst();
+ doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(), any(), any(),
+ any(), any());
+
+ mCurrentFolder.setFolderId(TEST_INBOX_FOLDER_ID);
+ when(mAccountItem.getUciFull()).thenReturn(TEST_FIRST_BT_UCI_RECIPIENT);
+
+ byte[] encodedMessageMime = mContent.getIMMessage(TEST_ID, mParams, mCurrentFolder);
+ InputStream inputStream = new ByteArrayInputStream(encodedMessageMime);
+ BluetoothMapbMessage messageMimeParsed = BluetoothMapbMessage.parse(inputStream, 1);
+
+ assertThat(messageMimeParsed.mAppParamCharset).isEqualTo(1);
+ assertThat(messageMimeParsed.getType()).isEqualTo(TYPE.IM);
+ assertThat(messageMimeParsed.getVersionString()).isEqualTo("VERSION:" +
+ mContent.mMessageVersion);
+ assertThat(messageMimeParsed.getFolder()).isEqualTo(mCurrentFolder.getFullPath());
+ assertThat(messageMimeParsed.getRecipients().size()).isEqualTo(1);
+ assertThat(messageMimeParsed.getOriginators().size()).isEqualTo(1);
+ assertThat(messageMimeParsed.getOriginators().get(0).getName()).isEqualTo(
+ 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(0);
+ 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(1);
+ 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(0);
+ 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(1);
+ assertThat(smsElement.getType()).isEqualTo(TYPE.SMS_GSM);
+ assertThat(smsElement.getLastActivity()).isEqualTo(TEST_DATE_SMS);
+ assertThat(smsElement.getName()).isEqualTo("");
+ assertThat(smsElement.getReadBool()).isTrue();
+ }
}
\ No newline at end of file
diff --git a/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapMasInstanceTest.java b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapMasInstanceTest.java
new file mode 100644
index 0000000..621c991
--- /dev/null
+++ b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapMasInstanceTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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 com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+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 BluetoothMapMasInstanceTest {
+ private static final int TEST_MAS_ID = 1;
+ private static final boolean TEST_ENABLE_SMS_MMS = true;
+ private static final String TEST_NAME = "test_name";
+ private static final String TEST_PACKAGE_NAME = "test.package.name";
+ private static final String TEST_ID = "1111";
+ private static final String TEST_PROVIDER_AUTHORITY = "test.project.provider";
+ private static final Drawable TEST_DRAWABLE = new ColorDrawable();
+ private static final BluetoothMapUtils.TYPE TEST_TYPE = BluetoothMapUtils.TYPE.EMAIL;
+ private static final String TEST_UCI = "uci";
+ private static final String TEST_UCI_PREFIX = "uci_prefix";
+
+ private BluetoothMapAccountItem mAccountItem;
+
+ @Mock
+ private Context mContext;
+ @Mock
+ private BluetoothMapService mMapService;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mAccountItem = BluetoothMapAccountItem.create(TEST_ID, TEST_NAME, TEST_PACKAGE_NAME,
+ TEST_PROVIDER_AUTHORITY, TEST_DRAWABLE, TEST_TYPE, TEST_UCI, TEST_UCI_PREFIX);
+ }
+
+ @Test
+ public void constructor_withNoParameters() {
+ BluetoothMapMasInstance instance = new BluetoothMapMasInstance();
+
+ assertThat(instance.mTag).isEqualTo(
+ "BluetoothMapMasInstance" + (BluetoothMapMasInstance.sInstanceCounter - 1));
+ }
+
+ @Test
+ public void toString_returnsInfo() {
+ BluetoothMapMasInstance instance = new BluetoothMapMasInstance(mMapService, mContext,
+ mAccountItem, TEST_MAS_ID, TEST_ENABLE_SMS_MMS);
+
+ String expected = "MasId: " + TEST_MAS_ID + " Uri:" + mAccountItem.mBase_uri + " SMS/MMS:"
+ + TEST_ENABLE_SMS_MMS;
+ assertThat(instance.toString()).isEqualTo(expected);
+ }
+}
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
new file mode 100644
index 0000000..0a6f6bd
--- /dev/null
+++ b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapObexServerTest.java
@@ -0,0 +1,195 @@
+/*
+ * 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 com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.ContentProviderClient;
+import android.content.Context;
+import android.database.MatrixCursor;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.RemoteException;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.bluetooth.BluetoothMethodProxy;
+import com.android.bluetooth.mapapi.BluetoothMapContract;
+import com.android.obex.ResponseCodes;
+
+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;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BluetoothMapObexServerTest {
+ private static final int TEST_MAS_ID = 1;
+ private static final boolean TEST_ENABLE_SMS_MMS = true;
+ private static final String TEST_NAME = "test_name";
+ private static final String TEST_PACKAGE_NAME = "test.package.name";
+ private static final String TEST_ID = "1111";
+ private static final String TEST_PROVIDER_AUTHORITY = "test.project.provider";
+ private static final Drawable TEST_DRAWABLE = new ColorDrawable();
+ private static final BluetoothMapUtils.TYPE TEST_TYPE = BluetoothMapUtils.TYPE.IM;
+ private static final String TEST_UCI = "uci";
+ private static final String TEST_UCI_PREFIX = "uci_prefix";
+
+ private BluetoothMapAccountItem mAccountItem;
+ private BluetoothMapMasInstance mMasInstance;
+ private BluetoothMapObexServer mObexServer;
+ private BluetoothMapAppParams mParams;
+
+ @Mock
+ private Context mContext;
+ @Mock
+ private BluetoothMapService mMapService;
+ @Mock
+ private ContentProviderClient mProviderClient;
+ @Mock
+ private BluetoothMapContentObserver mObserver;
+ @Spy
+ private BluetoothMethodProxy mMapMethodProxy = BluetoothMethodProxy.getInstance();
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ BluetoothMethodProxy.setInstanceForTesting(mMapMethodProxy);
+ doReturn(mProviderClient).when(
+ mMapMethodProxy).contentResolverAcquireUnstableContentProviderClient(any(), any());
+ mAccountItem = BluetoothMapAccountItem.create(TEST_ID, TEST_NAME, TEST_PACKAGE_NAME,
+ TEST_PROVIDER_AUTHORITY, TEST_DRAWABLE, TEST_TYPE, TEST_UCI, TEST_UCI_PREFIX);
+ mMasInstance = new BluetoothMapMasInstance(mMapService, mContext,
+ mAccountItem, TEST_MAS_ID, TEST_ENABLE_SMS_MMS);
+ mParams = new BluetoothMapAppParams();
+ mObexServer = new BluetoothMapObexServer(null, mContext, mObserver, mMasInstance,
+ mAccountItem, TEST_ENABLE_SMS_MMS);
+ }
+
+ @Test
+ public void setOwnerStatus_withAccountTypeEmail() throws Exception {
+ doReturn(null).when(mProviderClient).query(any(), any(), any(), any(), any());
+ 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);
+
+ assertThat(obexServer.setOwnerStatus(mParams)).isEqualTo(
+ ResponseCodes.OBEX_HTTP_UNAVAILABLE);
+ }
+
+ @Test
+ public void setOwnerStatus_withAppParamsInvalid() throws Exception {
+ BluetoothMapAppParams params = mock(BluetoothMapAppParams.class);
+ when(params.getPresenceAvailability()).thenReturn(
+ BluetoothMapAppParams.INVALID_VALUE_PARAMETER);
+ when(params.getPresenceStatus()).thenReturn(null);
+ when(params.getLastActivity()).thenReturn(
+ (long) BluetoothMapAppParams.INVALID_VALUE_PARAMETER);
+ when(params.getChatState()).thenReturn(BluetoothMapAppParams.INVALID_VALUE_PARAMETER);
+ when(params.getChatStateConvoIdString()).thenReturn(null);
+
+ assertThat(mObexServer.setOwnerStatus(params)).isEqualTo(
+ ResponseCodes.OBEX_HTTP_PRECON_FAILED);
+ }
+
+ @Test
+ public void setOwnerStatus_withNonNullBundle() throws Exception {
+ setUpBluetoothMapAppParams(mParams);
+ Bundle bundle = new Bundle();
+ when(mProviderClient.call(any(), any(), any())).thenReturn(bundle);
+
+ assertThat(mObexServer.setOwnerStatus(mParams)).isEqualTo(
+ ResponseCodes.OBEX_HTTP_OK);
+ }
+
+ @Test
+ public void setOwnerStatus_withNullBundle() throws Exception {
+ setUpBluetoothMapAppParams(mParams);
+ when(mProviderClient.call(any(), any(), any())).thenReturn(null);
+
+ assertThat(mObexServer.setOwnerStatus(mParams)).isEqualTo(
+ ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED);
+ }
+
+ @Test
+ public void setOwnerStatus_withRemoteExceptionThrown() throws Exception {
+ setUpBluetoothMapAppParams(mParams);
+ doThrow(RemoteException.class).when(mProviderClient).call(any(), any(), any());
+
+ assertThat(mObexServer.setOwnerStatus(mParams)).isEqualTo(
+ ResponseCodes.OBEX_HTTP_UNAVAILABLE);
+ }
+
+ @Test
+ public void setOwnerStatus_withNullPointerExceptionThrown() throws Exception {
+ setUpBluetoothMapAppParams(mParams);
+ doThrow(NullPointerException.class).when(mProviderClient).call(any(), any(), any());
+
+ assertThat(mObexServer.setOwnerStatus(mParams)).isEqualTo(
+ ResponseCodes.OBEX_HTTP_UNAVAILABLE);
+ }
+
+ @Test
+ public void setOwnerStatus_withIllegalArgumentExceptionThrown() throws Exception {
+ setUpBluetoothMapAppParams(mParams);
+ doThrow(IllegalArgumentException.class).when(mProviderClient).call(any(), any(), any());
+
+ assertThat(mObexServer.setOwnerStatus(mParams)).isEqualTo(
+ ResponseCodes.OBEX_HTTP_UNAVAILABLE);
+ }
+
+ @Test
+ public void addEmailFolders() throws Exception {
+ MatrixCursor cursor = new MatrixCursor(new String[]{BluetoothMapContract.FolderColumns.NAME,
+ BluetoothMapContract.FolderColumns._ID});
+ long parentId = 1;
+ long childId = 2;
+ cursor.addRow(new Object[]{"test_name", childId});
+ cursor.moveToFirst();
+ BluetoothMapFolderElement parentFolder = new BluetoothMapFolderElement("parent", null);
+ parentFolder.setFolderId(parentId);
+ doReturn(cursor).when(mProviderClient).query(any(), any(),
+ eq(BluetoothMapContract.FolderColumns.PARENT_FOLDER_ID + " = " + parentId), any(),
+ any());
+
+ mObexServer.addEmailFolders(parentFolder);
+
+ assertThat(parentFolder.getFolderById(childId)).isNotNull();
+ }
+
+ private void setUpBluetoothMapAppParams(BluetoothMapAppParams params) {
+ params.setPresenceAvailability(1);
+ params.setPresenceStatus("test_presence_status");
+ params.setLastActivity(0);
+ params.setChatState(1);
+ params.setChatStateConvoId(1, 1);
+ }
+}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapServiceTest.java
index 511aba1..1a3ed24 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapServiceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapServiceTest.java
@@ -15,24 +15,38 @@
*/
package com.android.bluetooth.map;
+import static com.android.bluetooth.map.BluetoothMapService.MSG_MAS_CONNECT_CANCEL;
+import static com.android.bluetooth.map.BluetoothMapService.UPDATE_MAS_INSTANCES;
+import static com.android.bluetooth.map.BluetoothMapService.USER_TIMEOUT;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.bluetooth.BluetoothAdapter;
-import android.content.Context;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
-import androidx.test.InstrumentationRegistry;
import androidx.test.filters.MediumTest;
import androidx.test.rule.ServiceTestRule;
import androidx.test.runner.AndroidJUnit4;
-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.junit.After;
-import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Rule;
@@ -44,9 +58,11 @@
@MediumTest
@RunWith(AndroidJUnit4.class)
public class BluetoothMapServiceTest {
+ private static final String REMOTE_DEVICE_ADDRESS = "00:00:00:00:00:00";
+
private BluetoothMapService mService = null;
private BluetoothAdapter mAdapter = null;
- private Context mTargetContext;
+ private BluetoothDevice mRemoteDevice;
@Rule public final ServiceTestRule mServiceRule = new ServiceTestRule();
@@ -55,7 +71,6 @@
@Before
public void setUp() throws Exception {
- mTargetContext = InstrumentationRegistry.getTargetContext();
Assume.assumeTrue("Ignore test when BluetoothMapService is not enabled",
BluetoothMapService.isEnabled());
MockitoAnnotations.initMocks(this);
@@ -64,10 +79,11 @@
doReturn(true, false).when(mAdapterService).isStartedProfile(anyString());
TestUtils.startService(mServiceRule, BluetoothMapService.class);
mService = BluetoothMapService.getBluetoothMapService();
- Assert.assertNotNull(mService);
+ assertThat(mService).isNotNull();
// Try getting the Bluetooth adapter
mAdapter = BluetoothAdapter.getDefaultAdapter();
- Assert.assertNotNull(mAdapter);
+ assertThat(mAdapter).isNotNull();
+ mRemoteDevice = mAdapter.getRemoteDevice(REMOTE_DEVICE_ADDRESS);
}
@After
@@ -77,12 +93,79 @@
}
TestUtils.stopService(mServiceRule, BluetoothMapService.class);
mService = BluetoothMapService.getBluetoothMapService();
- Assert.assertNull(mService);
+ assertThat(mService).isNull();
TestUtils.clearAdapterService(mAdapterService);
}
@Test
- public void testInitialize() {
- Assert.assertNotNull(BluetoothMapService.getBluetoothMapService());
+ public void initialize() {
+ assertThat(BluetoothMapService.getBluetoothMapService()).isNotNull();
+ }
+
+ @Test
+ public void getDevicesMatchingConnectionStates_whenNoDeviceIsConnected_returnsEmptyList() {
+ when(mAdapterService.getBondedDevices()).thenReturn(new BluetoothDevice[] {mRemoteDevice});
+
+ assertThat(mService.getDevicesMatchingConnectionStates(
+ new int[] {BluetoothProfile.STATE_CONNECTED})).isEmpty();
+ }
+
+ @Test
+ public void getNextMasId_isInRange() {
+ int masId = mService.getNextMasId();
+ assertThat(masId).isAtMost(0xff);
+ assertThat(masId).isAtLeast(1);
+ }
+
+ @Test
+ public void sendConnectCancelMessage() {
+ TestableHandler handler = spy(new TestableHandler(Looper.getMainLooper()));
+ mService.mSessionStatusHandler = handler;
+
+ mService.sendConnectCancelMessage();
+
+ verify(handler, timeout(1_000)).messageArrived(
+ eq(MSG_MAS_CONNECT_CANCEL), anyInt(), anyInt(), any());
+ }
+
+ @Test
+ public void sendConnectTimeoutMessage() {
+ TestableHandler handler = spy(new TestableHandler(Looper.getMainLooper()));
+ mService.mSessionStatusHandler = handler;
+
+ mService.sendConnectTimeoutMessage();
+
+ verify(handler, timeout(1_000)).messageArrived(
+ eq(USER_TIMEOUT), anyInt(), anyInt(), any());
+ }
+
+ @Test
+ public void updateMasInstances() {
+ int action = 5;
+ TestableHandler handler = spy(new TestableHandler(Looper.getMainLooper()));
+ mService.mSessionStatusHandler = handler;
+
+ mService.updateMasInstances(action);
+
+ verify(handler, timeout(1_000)).messageArrived(
+ eq(UPDATE_MAS_INSTANCES), eq(action), anyInt(), any());
+ }
+
+ public static class TestableHandler extends Handler {
+ public TestableHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ messageArrived(msg.what, msg.arg1, msg.arg2, msg.obj);
+ }
+
+ public void messageArrived(int what, int arg1, int arg2, Object obj) {}
+ }
+
+ @Test
+ public void testDumpDoesNotCrash() {
+ mService.dump(new StringBuilder());
}
}
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..c1d7bc8
--- /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.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() {
+ 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/map/BluetoothMapUtilsTest.java b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapUtilsTest.java
new file mode 100644
index 0000000..6a8eb18
--- /dev/null
+++ b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapUtilsTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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.map;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.database.MatrixCursor;
+
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.bluetooth.mapapi.BluetoothMapContract;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.charset.StandardCharsets;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class BluetoothMapUtilsTest {
+
+ private static final String TEXT = "코드";
+ private static final String QUOTED_PRINTABLE_ENCODED_TEXT = "=EC=BD=94=EB=93=9C";
+ private static final String BASE64_ENCODED_TEXT = "7L2U65Oc";
+
+ @Test
+ public void encodeQuotedPrintable_withNullInput_returnsNull() {
+ assertThat(BluetoothMapUtils.encodeQuotedPrintable(null)).isNull();
+ }
+
+ @Test
+ public void encodeQuotedPrintable() {
+ assertThat(BluetoothMapUtils.encodeQuotedPrintable(TEXT.getBytes(StandardCharsets.UTF_8)))
+ .isEqualTo(QUOTED_PRINTABLE_ENCODED_TEXT);
+ }
+
+ @Test
+ public void quotedPrintableToUtf8() {
+ assertThat(BluetoothMapUtils.quotedPrintableToUtf8(QUOTED_PRINTABLE_ENCODED_TEXT, null))
+ .isEqualTo(TEXT.getBytes(StandardCharsets.UTF_8));
+ }
+
+ @Test
+ public void printCursor_doesNotCrash() {
+ MatrixCursor cursor = new MatrixCursor(new String[] {
+ BluetoothMapContract.PresenceColumns.LAST_ONLINE, "Name"});
+ cursor.addRow(new Object[] {345345226L, "test_name"});
+
+ BluetoothMapUtils.printCursor(cursor);
+ }
+
+ @Test
+ public void stripEncoding_quotedPrintable() {
+ assertThat(BluetoothMapUtils.stripEncoding("=?UTF-8?Q?" + QUOTED_PRINTABLE_ENCODED_TEXT
+ + "?=")).isEqualTo(TEXT);
+ }
+
+ @Test
+ public void stripEncoding_base64() {
+ assertThat(BluetoothMapUtils.stripEncoding("=?UTF-8?B?" + BASE64_ENCODED_TEXT + "?="))
+ .isEqualTo(TEXT);
+ }
+}
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..3249a42 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
@@ -24,6 +24,7 @@
import android.bluetooth.IBluetoothMapClient;
import android.content.Context;
import android.os.UserHandle;
+import android.util.Log;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.MediumTest;
@@ -122,6 +123,8 @@
// is the statemachine created
Map<BluetoothDevice, MceStateMachine> map = mService.getInstanceMap();
+ Log.d("MapClientTest", "map=" + map);
+
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/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/McpServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/mcp/McpServiceTest.java
index 3374607..cfd051a 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/mcp/McpServiceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/mcp/McpServiceTest.java
@@ -136,4 +136,9 @@
}
});
}
+
+ @Test
+ public void testDumpDoesNotCrash() {
+ mMcpService.dump(new StringBuilder());
+ }
}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/mcp/MediaControlProfileTest.java b/android/app/tests/unit/src/com/android/bluetooth/mcp/MediaControlProfileTest.java
index 3ec632a..bed8b79 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/mcp/MediaControlProfileTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/mcp/MediaControlProfileTest.java
@@ -465,4 +465,9 @@
testGetSupportedPlayingOrder(false, true);
testGetSupportedPlayingOrder(false, false);
}
+
+ @Test
+ public void testDumpDoesNotCrash() {
+ mMediaControlProfile.dump(new StringBuilder());
+ }
}
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 07c88b8..e16e310 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
@@ -57,6 +57,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.ArgumentCaptor;
@@ -65,7 +66,6 @@
import java.io.File;
-
@MediumTest
@RunWith(AndroidJUnit4.class)
public class BluetoothOppLauncherActivityTest {
@@ -139,6 +139,7 @@
assertThat(argument.getValue().getData()).isEqualTo(Uri.EMPTY);
}
+ @Ignore("b/263724420")
@Test
public void launchDevicePicker_bluetoothNotEnabled_launchEnableActivity() throws Exception {
doReturn(false).when(mMethodProxy).bluetoothAdapterIsEnabled(any());
@@ -151,6 +152,7 @@
intended(hasComponent(BluetoothOppBtEnableActivity.class.getName()));
}
+ @Ignore("b/263724420")
@Test
public void launchDevicePicker_bluetoothEnabled_launchActivity() throws Exception {
doReturn(true).when(mMethodProxy).bluetoothAdapterIsEnabled(any());
@@ -185,6 +187,7 @@
assertThat(file.length()).isGreaterThan(shareContent.length());
}
+ @Ignore("b/263754734")
@Test
public void sendFileInfo_finishImmediately() throws Exception {
doReturn(true).when(mMethodProxy).bluetoothAdapterIsEnabled(any());
diff --git a/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppReceiverTest.java b/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppReceiverTest.java
index 242db8d..2f087f4 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppReceiverTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppReceiverTest.java
@@ -54,6 +54,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;
@@ -93,6 +94,7 @@
Intents.release();
}
+ @Ignore("b/262201478")
@Test
public void onReceive_withActionDeviceSelected_callsStartTransfer() {
BluetoothOppManager bluetoothOppManager = spy(BluetoothOppManager.getInstance(mContext));
diff --git a/android/app/tests/unit/src/com/android/bluetooth/pan/PanServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/pan/PanServiceTest.java
index a6736cf..fb33486 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/pan/PanServiceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/pan/PanServiceTest.java
@@ -15,27 +15,32 @@
*/
package com.android.bluetooth.pan;
+import static android.bluetooth.BluetoothPan.PAN_ROLE_NONE;
+import static android.net.TetheringManager.TETHERING_BLUETOOTH;
+import static android.net.TetheringManager.TETHER_ERROR_SERVICE_UNAVAIL;
+
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
-import android.content.Context;
+import android.bluetooth.BluetoothProfile;
+import android.net.TetheringInterface;
import android.os.UserManager;
-import androidx.test.InstrumentationRegistry;
import androidx.test.filters.MediumTest;
import androidx.test.rule.ServiceTestRule;
import androidx.test.runner.AndroidJUnit4;
-import com.android.bluetooth.R;
import com.android.bluetooth.TestUtils;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.storage.DatabaseManager;
+import com.android.bluetooth.pan.PanService.BluetoothPanDevice;
import org.junit.After;
-import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Rule;
@@ -47,9 +52,12 @@
@MediumTest
@RunWith(AndroidJUnit4.class)
public class PanServiceTest {
+ private static final String REMOTE_DEVICE_ADDRESS = "00:00:00:00:00:00";
+ private static final byte[] REMOTE_DEVICE_ADDRESS_AS_ARRAY = new byte[] {0, 0, 0, 0, 0, 0};
+
private PanService mService = null;
private BluetoothAdapter mAdapter = null;
- private Context mTargetContext;
+ private BluetoothDevice mRemoteDevice;
@Rule public final ServiceTestRule mServiceRule = new ServiceTestRule();
@@ -59,7 +67,6 @@
@Before
public void setUp() throws Exception {
- mTargetContext = InstrumentationRegistry.getTargetContext();
Assume.assumeTrue("Ignore test when PanService is not enabled",
PanService.isEnabled());
MockitoAnnotations.initMocks(this);
@@ -68,11 +75,12 @@
doReturn(true, false).when(mAdapterService).isStartedProfile(anyString());
TestUtils.startService(mServiceRule, PanService.class);
mService = PanService.getPanService();
- Assert.assertNotNull(mService);
+ assertThat(mService).isNotNull();
// Try getting the Bluetooth adapter
mAdapter = BluetoothAdapter.getDefaultAdapter();
- Assert.assertNotNull(mAdapter);
+ assertThat(mAdapter).isNotNull();
mService.mUserManager = mMockUserManager;
+ mRemoteDevice = mAdapter.getRemoteDevice(REMOTE_DEVICE_ADDRESS);
}
@After
@@ -82,19 +90,127 @@
}
TestUtils.stopService(mServiceRule, PanService.class);
mService = PanService.getPanService();
- Assert.assertNull(mService);
+ assertThat(mService).isNull();
TestUtils.clearAdapterService(mAdapterService);
}
@Test
- public void testInitialize() {
- Assert.assertNotNull(PanService.getPanService());
+ public void initialize() {
+ assertThat(PanService.getPanService()).isNotNull();
}
@Test
- public void testGuestUserConnect() {
- BluetoothDevice device = TestUtils.getTestDevice(mAdapter, 0);
+ public void connect_whenGuestUser_returnsFalse() {
when(mMockUserManager.isGuestUser()).thenReturn(true);
- Assert.assertFalse(mService.connect(device));
+ assertThat(mService.connect(mRemoteDevice)).isFalse();
+ }
+
+ @Test
+ public void connect_inConnectedState_returnsFalse() {
+ when(mMockUserManager.isGuestUser()).thenReturn(false);
+ mService.mPanDevices.put(mRemoteDevice, new BluetoothPanDevice(
+ BluetoothProfile.STATE_CONNECTED, "iface", PAN_ROLE_NONE, PAN_ROLE_NONE));
+
+ assertThat(mService.connect(mRemoteDevice)).isFalse();
+ }
+
+ @Test
+ public void connect() {
+ when(mMockUserManager.isGuestUser()).thenReturn(false);
+ mService.mPanDevices.put(mRemoteDevice, new BluetoothPanDevice(
+ BluetoothProfile.STATE_DISCONNECTED, "iface", PAN_ROLE_NONE, PAN_ROLE_NONE));
+
+ assertThat(mService.connect(mRemoteDevice)).isTrue();
+ }
+
+ @Test
+ public void disconnect_returnsTrue() {
+ assertThat(mService.disconnect(mRemoteDevice)).isTrue();
+ }
+
+ @Test
+ public void convertHalState() {
+ assertThat(PanService.convertHalState(PanService.CONN_STATE_CONNECTED))
+ .isEqualTo(BluetoothProfile.STATE_CONNECTED);
+ assertThat(PanService.convertHalState(PanService.CONN_STATE_CONNECTING))
+ .isEqualTo(BluetoothProfile.STATE_CONNECTING);
+ assertThat(PanService.convertHalState(PanService.CONN_STATE_DISCONNECTED))
+ .isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
+ assertThat(PanService.convertHalState(PanService.CONN_STATE_DISCONNECTING))
+ .isEqualTo(BluetoothProfile.STATE_DISCONNECTING);
+ assertThat(PanService.convertHalState(-24664)) // illegal value
+ .isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
+ }
+
+ @Test
+ public void dump() {
+ mService.mPanDevices.put(mRemoteDevice, new BluetoothPanDevice(
+ BluetoothProfile.STATE_DISCONNECTED, "iface", PAN_ROLE_NONE, PAN_ROLE_NONE));
+
+ mService.dump(new StringBuilder());
+ }
+
+ @Test
+ public void onConnectStateChanged_doesNotCrash() {
+ mService.onConnectStateChanged(REMOTE_DEVICE_ADDRESS_AS_ARRAY, 1, 2, 3, 4);
+ }
+
+ @Test
+ public void onControlStateChanged_doesNotCrash() {
+ mService.onControlStateChanged(1, 2, 3, "ifname");
+ }
+
+ @Test
+ public void setConnectionPolicy_whenDatabaseManagerRefuses_returnsFalse() {
+ int connectionPolicy = BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+ when(mDatabaseManager.setProfileConnectionPolicy(
+ mRemoteDevice, BluetoothProfile.PAN, connectionPolicy)).thenReturn(false);
+
+ assertThat(mService.setConnectionPolicy(mRemoteDevice, connectionPolicy)).isFalse();
+ }
+
+ @Test
+ public void setConnectionPolicy_returnsTrue() {
+ when(mDatabaseManager.setProfileConnectionPolicy(
+ mRemoteDevice, BluetoothProfile.PAN, BluetoothProfile.CONNECTION_POLICY_ALLOWED))
+ .thenReturn(true);
+ assertThat(mService.setConnectionPolicy(
+ mRemoteDevice, BluetoothProfile.CONNECTION_POLICY_ALLOWED)).isTrue();
+
+ when(mDatabaseManager.setProfileConnectionPolicy(
+ mRemoteDevice, BluetoothProfile.PAN, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN))
+ .thenReturn(true);
+ assertThat(mService.setConnectionPolicy(
+ mRemoteDevice, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN)).isTrue();
+ }
+
+ @Test
+ public void connectState_constructor() {
+ int state = 1;
+ int error = 2;
+ int localRole = 3;
+ int remoteRole = 4;
+
+ PanService.ConnectState connectState = new PanService.ConnectState(
+ REMOTE_DEVICE_ADDRESS_AS_ARRAY, state, error, localRole, remoteRole);
+
+ assertThat(connectState.addr).isEqualTo(REMOTE_DEVICE_ADDRESS_AS_ARRAY);
+ assertThat(connectState.state).isEqualTo(state);
+ assertThat(connectState.error).isEqualTo(error);
+ assertThat(connectState.local_role).isEqualTo(localRole);
+ assertThat(connectState.remote_role).isEqualTo(remoteRole);
+ }
+
+ @Test
+ public void tetheringCallback_onError_clearsPanDevices() {
+ mService.mIsTethering = true;
+ mService.mPanDevices.put(mRemoteDevice, new BluetoothPanDevice(
+ BluetoothProfile.STATE_DISCONNECTED, "iface", PAN_ROLE_NONE, PAN_ROLE_NONE));
+ TetheringInterface iface = new TetheringInterface(TETHERING_BLUETOOTH, "iface");
+
+ mService.mTetheringCallback.onError(iface, TETHER_ERROR_SERVICE_UNAVAIL);
+
+ assertThat(mService.mPanDevices).isEmpty();
+ assertThat(mService.mIsTethering).isFalse();
}
}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/pbap/BluetoothPbapObexServerTest.java b/android/app/tests/unit/src/com/android/bluetooth/pbap/BluetoothPbapObexServerTest.java
index 37a449b..62834b4 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/pbap/BluetoothPbapObexServerTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/pbap/BluetoothPbapObexServerTest.java
@@ -847,4 +847,10 @@
assertThat(mServer.parseApplicationParameter(rawBytes, appParamValue)).isTrue();
assertThat(appParamValue.vCardSelectorOperator).isEqualTo("1");
}
+
+ @Test
+ public void appParamValueDump_doesNotCrash() {
+ AppParamValue appParamValue = new AppParamValue();
+ appParamValue.dump();
+ }
}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/pbap/BluetoothPbapServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/pbap/BluetoothPbapServiceTest.java
index 75fb5ed..44aedc1 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/pbap/BluetoothPbapServiceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/pbap/BluetoothPbapServiceTest.java
@@ -15,38 +15,47 @@
*/
package com.android.bluetooth.pbap;
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.bluetooth.BluetoothAdapter;
-import android.content.Context;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.content.Intent;
+import android.os.Message;
-import androidx.test.InstrumentationRegistry;
import androidx.test.filters.MediumTest;
import androidx.test.rule.ServiceTestRule;
import androidx.test.runner.AndroidJUnit4;
-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.junit.After;
-import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@MediumTest
@RunWith(AndroidJUnit4.class)
public class BluetoothPbapServiceTest {
+ private static final String REMOTE_DEVICE_ADDRESS = "00:00:00:00:00:00";
+
private BluetoothPbapService mService;
private BluetoothAdapter mAdapter = null;
- private Context mTargetContext;
+ private BluetoothDevice mRemoteDevice;
@Rule public final ServiceTestRule mServiceRule = new ServiceTestRule();
@@ -55,7 +64,6 @@
@Before
public void setUp() throws Exception {
- mTargetContext = InstrumentationRegistry.getTargetContext();
Assume.assumeTrue("Ignore test when BluetoothPbapService is not enabled",
BluetoothPbapService.isEnabled());
MockitoAnnotations.initMocks(this);
@@ -64,10 +72,11 @@
doReturn(true, false).when(mAdapterService).isStartedProfile(anyString());
TestUtils.startService(mServiceRule, BluetoothPbapService.class);
mService = BluetoothPbapService.getBluetoothPbapService();
- Assert.assertNotNull(mService);
+ assertThat(mService).isNotNull();
// Try getting the Bluetooth adapter
mAdapter = BluetoothAdapter.getDefaultAdapter();
- Assert.assertNotNull(mAdapter);
+ assertThat(mAdapter).isNotNull();
+ mRemoteDevice = mAdapter.getRemoteDevice(REMOTE_DEVICE_ADDRESS);
}
@After
@@ -77,12 +86,122 @@
}
TestUtils.stopService(mServiceRule, BluetoothPbapService.class);
mService = BluetoothPbapService.getBluetoothPbapService();
- Assert.assertNull(mService);
+ assertThat(mService).isNull();
TestUtils.clearAdapterService(mAdapterService);
}
@Test
- public void testInitialize() {
- Assert.assertNotNull(BluetoothPbapService.getBluetoothPbapService());
+ public void initialize() {
+ assertThat(BluetoothPbapService.getBluetoothPbapService()).isNotNull();
+ }
+
+ @Test
+ public void disconnect() {
+ PbapStateMachine sm = mock(PbapStateMachine.class);
+ mService.mPbapStateMachineMap.put(mRemoteDevice, sm);
+
+ mService.disconnect(mRemoteDevice);
+
+ verify(sm).sendMessage(PbapStateMachine.DISCONNECT);
+ }
+
+ @Test
+ public void getConnectedDevices() {
+ PbapStateMachine sm = mock(PbapStateMachine.class);
+ mService.mPbapStateMachineMap.put(mRemoteDevice, sm);
+
+ assertThat(mService.getConnectedDevices()).contains(mRemoteDevice);
+ }
+
+ @Test
+ public void getConnectionPolicy_withDeviceIsNull_throwsNPE() {
+ assertThrows(IllegalArgumentException.class, () -> mService.getConnectionPolicy(null));
+ }
+
+ @Test
+ public void getConnectionPolicy() {
+ mService.getConnectionPolicy(mRemoteDevice);
+
+ verify(mDatabaseManager).getProfileConnectionPolicy(mRemoteDevice, BluetoothProfile.PBAP);
+ }
+
+ @Test
+ public void getDevicesMatchingConnectionStates_whenStatesIsNull_returnsEmptyList() {
+ assertThat(mService.getDevicesMatchingConnectionStates(null)).isEmpty();
+ }
+
+ @Test
+ public void getDevicesMatchingConnectionStates() {
+ PbapStateMachine sm = mock(PbapStateMachine.class);
+ mService.mPbapStateMachineMap.put(mRemoteDevice, sm);
+ when(sm.getConnectionState()).thenReturn(BluetoothProfile.STATE_CONNECTED);
+
+ int[] states = new int[] {BluetoothProfile.STATE_CONNECTED};
+ assertThat(mService.getDevicesMatchingConnectionStates(states)).contains(mRemoteDevice);
+ }
+
+ @Test
+ public void onAcceptFailed() {
+ PbapStateMachine sm = mock(PbapStateMachine.class);
+ mService.mPbapStateMachineMap.put(mRemoteDevice, sm);
+
+ mService.onAcceptFailed();
+
+ assertThat(mService.mPbapStateMachineMap).isEmpty();
+ }
+
+ @Test
+ public void broadcastReceiver_onReceive_withActionConnectionAccessReply() {
+ Intent intent = new Intent(BluetoothDevice.ACTION_CONNECTION_ACCESS_REPLY);
+ intent.putExtra(BluetoothDevice.EXTRA_ACCESS_REQUEST_TYPE,
+ BluetoothDevice.REQUEST_TYPE_PHONEBOOK_ACCESS);
+ intent.putExtra(BluetoothDevice.EXTRA_CONNECTION_ACCESS_RESULT,
+ BluetoothDevice.CONNECTION_ACCESS_YES);
+ intent.putExtra(BluetoothDevice.EXTRA_ALWAYS_ALLOWED, true);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevice);
+ PbapStateMachine sm = mock(PbapStateMachine.class);
+ mService.mPbapStateMachineMap.put(mRemoteDevice, sm);
+
+ mService.mPbapReceiver.onReceive(null, intent);
+
+ verify(sm).sendMessage(PbapStateMachine.AUTHORIZED);
+ }
+
+ @Test
+ public void broadcastReceiver_onReceive_withActionAuthResponse() {
+ Intent intent = new Intent(BluetoothPbapService.AUTH_RESPONSE_ACTION);
+ String sessionKey = "test_session_key";
+ intent.putExtra(BluetoothPbapService.EXTRA_SESSION_KEY, sessionKey);
+ intent.putExtra(BluetoothPbapService.EXTRA_DEVICE, mRemoteDevice);
+ PbapStateMachine sm = mock(PbapStateMachine.class);
+ mService.mPbapStateMachineMap.put(mRemoteDevice, sm);
+
+ mService.mPbapReceiver.onReceive(null, intent);
+
+ ArgumentCaptor<Message> captor = ArgumentCaptor.forClass(Message.class);
+ verify(sm).sendMessage(captor.capture());
+ Message msg = captor.getValue();
+ assertThat(msg.what).isEqualTo(PbapStateMachine.AUTH_KEY_INPUT);
+ assertThat(msg.obj).isEqualTo(sessionKey);
+ msg.recycle();
+ }
+
+ @Test
+ public void broadcastReceiver_onReceive_withActionAuthCancelled() {
+ Intent intent = new Intent(BluetoothPbapService.AUTH_CANCELLED_ACTION);
+ intent.putExtra(BluetoothPbapService.EXTRA_DEVICE, mRemoteDevice);
+ PbapStateMachine sm = mock(PbapStateMachine.class);
+ mService.mPbapStateMachineMap.put(mRemoteDevice, sm);
+
+ mService.mPbapReceiver.onReceive(null, intent);
+
+ verify(sm).sendMessage(PbapStateMachine.AUTH_CANCELLED);
+ }
+
+ @Test
+ public void broadcastReceiver_onReceive_withIllegalAction_doesNothing() {
+ Intent intent = new Intent("test_random_action");
+
+ mService.mPbapReceiver.onReceive(null, intent);
}
}
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/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/system/bta/csis/csis_client.cc b/system/bta/csis/csis_client.cc
index 8bc1190..ac1ce0d 100644
--- a/system/bta/csis/csis_client.cc
+++ b/system/bta/csis/csis_client.cc
@@ -984,7 +984,11 @@
return;
}
- csis_group->SetDesiredSize(value[0]);
+ auto new_size = value[0];
+ csis_group->SetDesiredSize(new_size);
+ if (new_size > csis_group->GetCurrentSize()) {
+ CsisActiveDiscovery(csis_group);
+ }
}
void OnCsisLockReadRsp(uint16_t conn_id, tGATT_STATUS status, uint16_t handle,
@@ -1127,14 +1131,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));
diff --git a/system/bta/dm/bta_dm_act.cc b/system/bta/dm/bta_dm_act.cc
index 1896cc9..3cbb536 100644
--- a/system/bta/dm/bta_dm_act.cc
+++ b/system/bta/dm/bta_dm_act.cc
@@ -1861,6 +1861,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 +2547,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--;
diff --git a/system/bta/dm/bta_dm_api.cc b/system/bta/dm/bta_dm_api.cc
index eded208..0703594 100644
--- a/system/bta/dm/bta_dm_api.cc
+++ b/system/bta/dm/bta_dm_api.cc
@@ -689,3 +689,14 @@
APPL_TRACE_API("BTA_DmBleResetId");
do_in_main_thread(FROM_HERE, base::Bind(bta_dm_ble_reset_id));
}
+
+bool BTA_DmCheckLeAudioCapable(const RawAddress& address) {
+ for (tBTM_INQ_INFO* inq_ent = BTM_InqDbFirst(); inq_ent != nullptr;
+ inq_ent = BTM_InqDbNext(inq_ent)) {
+ if (inq_ent->results.remote_bd_addr != address) continue;
+
+ LOG_INFO("Device is LE Audio capable based on AD content");
+ return inq_ent->results.ble_ad_is_le_audio_capable;
+ }
+ return false;
+}
\ No newline at end of file
diff --git a/system/bta/dm/bta_dm_cfg.cc b/system/bta/dm/bta_dm_cfg.cc
index 4a05f8e..6da7c43 100644
--- a/system/bta/dm/bta_dm_cfg.cc
+++ b/system/bta/dm/bta_dm_cfg.cc
@@ -26,12 +26,12 @@
#include <cstdint>
#include "bt_target.h" // Must be first to define build configuration
-
#include "bta/dm/bta_dm_int.h"
#include "bta/include/bta_api.h"
#include "bta/include/bta_hh_api.h"
#include "bta/include/bta_jv_api.h"
#include "bta/sys/bta_sys.h"
+#include "osi/include/properties.h"
#include "types/raw_address.h"
/* page timeout in 625uS */
@@ -44,14 +44,6 @@
#define BTA_DM_AVOID_SCATTER_A2DP TRUE
#endif
-/* For Insight, PM cfg lookup tables are runtime configurable (to allow tweaking
- * of params for power consumption measurements) */
-#ifndef BTE_SIM_APP
-#define tBTA_DM_PM_TYPE_QUALIFIER const
-#else
-#define tBTA_DM_PM_TYPE_QUALIFIER
-#endif
-
const tBTA_DM_CFG bta_dm_cfg = {
/* page timeout in 625uS */
BTA_DM_PAGE_TIMEOUT,
@@ -134,317 +126,257 @@
{BTA_ID_GATTS, BTA_ALL_APP_ID, 15} /* gatts spec table */
};
-tBTA_DM_PM_TYPE_QUALIFIER tBTA_DM_PM_SPEC bta_dm_pm_spec[BTA_DM_NUM_PM_SPEC] = {
- /* AG : 0 */
- {(BTA_DM_PM_SNIFF | BTA_DM_PM_PARK), /* allow park & sniff */
- (BTA_DM_PM_SSR2), /* the SSR entry */
- {
- {{BTA_DM_PM_SNIFF_A2DP_IDX, 7000},
- {BTA_DM_PM_NO_ACTION, 0}}, /* conn open sniff */
- {{BTA_DM_PM_NO_PREF, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* conn close */
- {{BTA_DM_PM_NO_ACTION, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* app open */
- {{BTA_DM_PM_NO_ACTION, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* app close */
- {{BTA_DM_PM_SNIFF_SCO_OPEN_IDX, 7000},
- {BTA_DM_PM_NO_ACTION, 0}}, /* sco open, active */
- {{BTA_DM_PM_SNIFF_A2DP_IDX, 7000},
- {BTA_DM_PM_NO_ACTION, 0}}, /* sco close sniff */
- {{BTA_DM_PM_SNIFF_A2DP_IDX, 7000},
- {BTA_DM_PM_NO_ACTION, 0}}, /* idle */
- {{BTA_DM_PM_ACTIVE, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* busy */
- {{BTA_DM_PM_RETRY, 7000},
- {BTA_DM_PM_NO_ACTION, 0}} /* mode change retry */
- }},
+tBTA_DM_PM_TYPE_QUALIFIER tBTA_DM_PM_SPEC* get_bta_dm_pm_spec() {
+ static uint16_t hs_sniff_delay = uint16_t(
+ osi_property_get_int32("bluetooth.bta_hs_sniff_delay_ms.config", 7000));
- /* CT, CG : 1 */
- {(BTA_DM_PM_SNIFF | BTA_DM_PM_PARK), /* allow park & sniff */
- (BTA_DM_PM_SSR2), /* the SSR entry */
- {
- {{BTA_DM_PM_PARK, 5000},
- {BTA_DM_PM_NO_ACTION, 0}}, /* conn open park */
- {{BTA_DM_PM_NO_PREF, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* conn close */
- {{BTA_DM_PM_NO_ACTION, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* app open */
- {{BTA_DM_PM_NO_ACTION, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* app close */
- {{BTA_DM_PM_SNIFF_A2DP_IDX, 5000},
- {BTA_DM_PM_NO_ACTION, 0}}, /* sco open sniff */
- {{BTA_DM_PM_PARK, 5000},
- {BTA_DM_PM_NO_ACTION, 0}}, /* sco close park */
- {{BTA_DM_PM_NO_ACTION, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* idle */
- {{BTA_DM_PM_NO_ACTION, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* busy */
- {{BTA_DM_PM_RETRY, 5000},
- {BTA_DM_PM_NO_ACTION, 0}} /* mode change retry */
- }},
+ static tBTA_DM_PM_TYPE_QUALIFIER tBTA_DM_PM_SPEC
+ bta_dm_pm_spec[BTA_DM_NUM_PM_SPEC] = {
+ /* AG : 0 */
+ {(BTA_DM_PM_SNIFF | BTA_DM_PM_PARK), /* allow park & sniff */
+ (BTA_DM_PM_SSR2), /* the SSR entry */
+ {
+ {BTA_DM_PM_SNIFF_A2DP_IDX, 7000}, /* conn open sniff */
+ {BTA_DM_PM_NO_PREF, 0}, /* conn close */
+ {BTA_DM_PM_NO_ACTION, 0}, /* app open */
+ {BTA_DM_PM_NO_ACTION, 0}, /* app close */
+ {BTA_DM_PM_SNIFF_SCO_OPEN_IDX, 7000}, /* sco open, active */
+ {BTA_DM_PM_SNIFF_A2DP_IDX, 7000}, /* sco close sniff */
+ {BTA_DM_PM_SNIFF_A2DP_IDX, 7000}, /* idle */
+ {BTA_DM_PM_ACTIVE, 0}, /* busy */
+ {BTA_DM_PM_RETRY, 7000} /* mode change retry */
+ }},
- /* DG, PBC : 2 */
- {(BTA_DM_PM_ACTIVE), /* no power saving mode allowed */
- (BTA_DM_PM_SSR2), /* the SSR entry */
- {
- {{BTA_DM_PM_SNIFF, 5000},
- {BTA_DM_PM_NO_ACTION, 0}}, /* conn open active */
- {{BTA_DM_PM_NO_PREF, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* conn close */
- {{BTA_DM_PM_ACTIVE, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* app open */
- {{BTA_DM_PM_NO_ACTION, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* app close */
- {{BTA_DM_PM_NO_ACTION, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* sco open */
- {{BTA_DM_PM_NO_ACTION, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* sco close */
- {{BTA_DM_PM_SNIFF, 1000}, {BTA_DM_PM_NO_ACTION, 0}}, /* idle */
- {{BTA_DM_PM_ACTIVE, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* busy */
- {{BTA_DM_PM_NO_ACTION, 0},
- {BTA_DM_PM_NO_ACTION, 0}} /* mode change retry */
- }},
+ /* CT, CG : 1 */
+ {(BTA_DM_PM_SNIFF | BTA_DM_PM_PARK), /* allow park & sniff */
+ (BTA_DM_PM_SSR2), /* the SSR entry */
+ {
+ {BTA_DM_PM_PARK, 5000}, /* conn open park */
+ {BTA_DM_PM_NO_PREF, 0}, /* conn close */
+ {BTA_DM_PM_NO_ACTION, 0}, /* app open */
+ {BTA_DM_PM_NO_ACTION, 0}, /* app close */
+ {BTA_DM_PM_SNIFF_A2DP_IDX, 5000}, /* sco open sniff */
+ {BTA_DM_PM_PARK, 5000}, /* sco close park */
+ {BTA_DM_PM_NO_ACTION, 0}, /* idle */
+ {BTA_DM_PM_NO_ACTION, 0}, /* busy */
+ {BTA_DM_PM_RETRY, 5000} /* mode change retry */
+ }},
- /* HD : 3 */
- {(BTA_DM_PM_SNIFF | BTA_DM_PM_PARK), /* allow park & sniff */
- (BTA_DM_PM_SSR3), /* the SSR entry */
- {
- {{BTA_DM_PM_SNIFF_HD_ACTIVE_IDX, 5000},
- {BTA_DM_PM_NO_ACTION, 0}}, /* conn open sniff */
- {{BTA_DM_PM_NO_PREF, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* conn close */
- {{BTA_DM_PM_NO_ACTION, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* app open */
- {{BTA_DM_PM_NO_ACTION, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* app close */
- {{BTA_DM_PM_NO_ACTION, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* sco open */
- {{BTA_DM_PM_NO_ACTION, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* sco close */
- {{BTA_DM_PM_SNIFF_HD_IDLE_IDX, 5000},
- {BTA_DM_PM_NO_ACTION, 0}}, /* idle */
- {{BTA_DM_PM_SNIFF_HD_ACTIVE_IDX, 0},
- {BTA_DM_PM_NO_ACTION, 0}}, /* busy */
- {{BTA_DM_PM_NO_ACTION, 0},
- {BTA_DM_PM_NO_ACTION, 0}} /* mode change retry */
- }},
+ /* DG, PBC : 2 */
+ {(BTA_DM_PM_ACTIVE), /* no power saving mode allowed */
+ (BTA_DM_PM_SSR2), /* the SSR entry */
+ {
+ {BTA_DM_PM_SNIFF, 5000}, /* conn open active */
+ {BTA_DM_PM_NO_PREF, 0}, /* conn close */
+ {BTA_DM_PM_ACTIVE, 0}, /* app open */
+ {BTA_DM_PM_NO_ACTION, 0}, /* app close */
+ {BTA_DM_PM_NO_ACTION, 0}, /* sco open */
+ {BTA_DM_PM_NO_ACTION, 0}, /* sco close */
+ {BTA_DM_PM_SNIFF, 1000}, /* idle */
+ {BTA_DM_PM_ACTIVE, 0}, /* busy */
+ {BTA_DM_PM_NO_ACTION, 0} /* mode change retry */
+ }},
- /* AV : 4 */
- {(BTA_DM_PM_SNIFF), /* allow sniff */
- (BTA_DM_PM_SSR2), /* the SSR entry */
- {
- {{BTA_DM_PM_SNIFF_A2DP_IDX, 7000},
- {BTA_DM_PM_NO_ACTION, 0}}, /* conn open sniff */
- {{BTA_DM_PM_NO_PREF, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* conn close */
- {{BTA_DM_PM_NO_ACTION, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* app open */
- {{BTA_DM_PM_NO_ACTION, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* app close */
- {{BTA_DM_PM_NO_ACTION, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* sco open */
- {{BTA_DM_PM_NO_ACTION, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* sco close */
- {{BTA_DM_PM_SNIFF_A2DP_IDX, 7000},
- {BTA_DM_PM_NO_ACTION, 0}}, /* idle */
- {{BTA_DM_PM_ACTIVE, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* busy */
- {{BTA_DM_PM_NO_ACTION, 0},
- {BTA_DM_PM_NO_ACTION, 0}} /* mode change retry */
- }},
+ /* HD : 3 */
+ {(BTA_DM_PM_SNIFF | BTA_DM_PM_PARK), /* allow park & sniff */
+ (BTA_DM_PM_SSR3), /* the SSR entry */
+ {
+ {BTA_DM_PM_SNIFF_HD_ACTIVE_IDX, 5000}, /* conn open sniff */
+ {BTA_DM_PM_NO_PREF, 0}, /* conn close */
+ {BTA_DM_PM_NO_ACTION, 0}, /* app open */
+ {BTA_DM_PM_NO_ACTION, 0}, /* app close */
+ {BTA_DM_PM_NO_ACTION, 0}, /* sco open */
+ {BTA_DM_PM_NO_ACTION, 0}, /* sco close */
+ {BTA_DM_PM_SNIFF_HD_IDLE_IDX, 5000}, /* idle */
+ {BTA_DM_PM_SNIFF_HD_ACTIVE_IDX, 0}, /* busy */
+ {BTA_DM_PM_NO_ACTION, 0} /* mode change retry */
+ }},
- /* HH for joysticks and gamepad : 5 */
- {(BTA_DM_PM_SNIFF | BTA_DM_PM_PARK), /* allow park & sniff */
- (BTA_DM_PM_SSR1), /* the SSR entry */
- {
- {{BTA_DM_PM_SNIFF6, BTA_DM_PM_HH_OPEN_DELAY},
- {BTA_DM_PM_NO_ACTION, 0}}, /* conn open sniff */
- {{BTA_DM_PM_NO_PREF, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* conn close */
- {{BTA_DM_PM_NO_ACTION, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* app open */
- {{BTA_DM_PM_NO_ACTION, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* app close */
- {{BTA_DM_PM_NO_ACTION, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* sco open */
- {{BTA_DM_PM_NO_ACTION, 0},
- {BTA_DM_PM_NO_ACTION, 0}}, /* sco close, used for HH suspend */
- {{BTA_DM_PM_SNIFF6, BTA_DM_PM_HH_IDLE_DELAY},
- {BTA_DM_PM_NO_ACTION, 0}}, /* idle */
- {{BTA_DM_PM_SNIFF6, BTA_DM_PM_HH_ACTIVE_DELAY},
- {BTA_DM_PM_NO_ACTION, 0}}, /* busy */
- {{BTA_DM_PM_NO_ACTION, 0},
- {BTA_DM_PM_NO_ACTION, 0}} /* mode change retry */
- }},
+ /* AV : 4 */
+ {(BTA_DM_PM_SNIFF), /* allow sniff */
+ (BTA_DM_PM_SSR2), /* the SSR entry */
+ {
+ {BTA_DM_PM_SNIFF_A2DP_IDX, 7000}, /* conn open sniff */
+ {BTA_DM_PM_NO_PREF, 0}, /* conn close */
+ {BTA_DM_PM_NO_ACTION, 0}, /* app open */
+ {BTA_DM_PM_NO_ACTION, 0}, /* app close */
+ {BTA_DM_PM_NO_ACTION, 0}, /* sco open */
+ {BTA_DM_PM_NO_ACTION, 0}, /* sco close */
+ {BTA_DM_PM_SNIFF_A2DP_IDX, 7000}, /* idle */
+ {BTA_DM_PM_ACTIVE, 0}, /* busy */
+ {BTA_DM_PM_NO_ACTION, 0} /* mode change retry */
+ }},
- /* HH : 6 */
- {(BTA_DM_PM_SNIFF | BTA_DM_PM_PARK), /* allow park & sniff */
- (BTA_DM_PM_SSR1), /* the SSR entry */
- {
- {{BTA_DM_PM_SNIFF_HH_OPEN_IDX, BTA_DM_PM_HH_OPEN_DELAY},
- {BTA_DM_PM_NO_ACTION, 0}}, /* conn open sniff */
- {{BTA_DM_PM_NO_PREF, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* conn close */
- {{BTA_DM_PM_NO_ACTION, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* app open */
- {{BTA_DM_PM_NO_ACTION, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* app close */
- {{BTA_DM_PM_NO_ACTION, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* sco open */
- {{BTA_DM_PM_NO_ACTION, 0},
- {BTA_DM_PM_NO_ACTION, 0}}, /* sco close, used for HH suspend */
- {{BTA_DM_PM_SNIFF_HH_IDLE_IDX, BTA_DM_PM_HH_IDLE_DELAY},
- {BTA_DM_PM_NO_ACTION, 0}}, /* idle */
- {{BTA_DM_PM_SNIFF_HH_ACTIVE_IDX, BTA_DM_PM_HH_ACTIVE_DELAY},
- {BTA_DM_PM_NO_ACTION, 0}}, /* busy */
- {{BTA_DM_PM_NO_ACTION, 0},
- {BTA_DM_PM_NO_ACTION, 0}} /* mode change retry */
- }},
+ /* HH for joysticks and gamepad : 5 */
+ {(BTA_DM_PM_SNIFF | BTA_DM_PM_PARK), /* allow park & sniff */
+ (BTA_DM_PM_SSR1), /* the SSR entry */
+ {
+ {BTA_DM_PM_SNIFF6,
+ BTA_DM_PM_HH_OPEN_DELAY}, /* conn open sniff */
+ {BTA_DM_PM_NO_PREF, 0}, /* conn close */
+ {BTA_DM_PM_NO_ACTION, 0}, /* app open */
+ {BTA_DM_PM_NO_ACTION, 0}, /* app close */
+ {BTA_DM_PM_NO_ACTION, 0}, /* sco open */
+ {BTA_DM_PM_NO_ACTION, 0}, /* sco close, used for HH suspend */
+ {BTA_DM_PM_SNIFF6, BTA_DM_PM_HH_IDLE_DELAY}, /* idle */
+ {BTA_DM_PM_SNIFF6, BTA_DM_PM_HH_ACTIVE_DELAY}, /* busy */
+ {BTA_DM_PM_NO_ACTION, 0} /* mode change retry */
+ }},
- /* FTC, OPC, JV : 7 */
- {(BTA_DM_PM_SNIFF), /* allow sniff */
- (BTA_DM_PM_SSR2), /* the SSR entry */
- {
- {{BTA_DM_PM_ACTIVE, 0},
- {BTA_DM_PM_NO_ACTION, 0}}, /* conn open active */
- {{BTA_DM_PM_NO_PREF, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* conn close */
- {{BTA_DM_PM_ACTIVE, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* app open */
- {{BTA_DM_PM_NO_ACTION, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* app close */
- {{BTA_DM_PM_NO_ACTION, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* sco open */
- {{BTA_DM_PM_NO_ACTION, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* sco close */
- {{BTA_DM_PM_SNIFF_A2DP_IDX, BTA_FTC_IDLE_TO_SNIFF_DELAY_MS},
- {BTA_DM_PM_NO_ACTION, 0}}, /* idle */
- {{BTA_DM_PM_ACTIVE, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* busy */
- {{BTA_DM_PM_NO_ACTION, 0},
- {BTA_DM_PM_NO_ACTION, 0}} /* mode change retry */
- }},
+ /* HH : 6 */
+ {(BTA_DM_PM_SNIFF | BTA_DM_PM_PARK), /* allow park & sniff */
+ (BTA_DM_PM_SSR1), /* the SSR entry */
+ {
+ {BTA_DM_PM_SNIFF_HH_OPEN_IDX,
+ BTA_DM_PM_HH_OPEN_DELAY}, /* conn open sniff */
+ {BTA_DM_PM_NO_PREF, 0}, /* conn close */
+ {BTA_DM_PM_NO_ACTION, 0}, /* app open */
+ {BTA_DM_PM_NO_ACTION, 0}, /* app close */
+ {BTA_DM_PM_NO_ACTION, 0}, /* sco open */
+ {BTA_DM_PM_NO_ACTION, 0}, /* sco close, used for HH suspend */
+ {BTA_DM_PM_SNIFF_HH_IDLE_IDX,
+ BTA_DM_PM_HH_IDLE_DELAY}, /* idle */
+ {BTA_DM_PM_SNIFF_HH_ACTIVE_IDX,
+ BTA_DM_PM_HH_ACTIVE_DELAY}, /* busy */
+ {BTA_DM_PM_NO_ACTION, 0} /* mode change retry */
+ }},
- /* FTS, PBS, OPS, MSE, BTA_JV_PM_ID_1 : 8 */
- {(BTA_DM_PM_SNIFF), /* allow sniff */
- (BTA_DM_PM_SSR2), /* the SSR entry */
- {
- {{BTA_DM_PM_ACTIVE, 0},
- {BTA_DM_PM_NO_ACTION, 0}}, /* conn open active */
- {{BTA_DM_PM_NO_PREF, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* conn close */
- {{BTA_DM_PM_ACTIVE, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* app open */
- {{BTA_DM_PM_NO_ACTION, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* app close */
- {{BTA_DM_PM_NO_ACTION, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* sco open */
- {{BTA_DM_PM_NO_ACTION, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* sco close */
- {{BTA_DM_PM_SNIFF_A2DP_IDX, BTA_FTS_OPS_IDLE_TO_SNIFF_DELAY_MS},
- {BTA_DM_PM_NO_ACTION, 0}}, /* idle */
- {{BTA_DM_PM_ACTIVE, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* busy */
- {{BTA_DM_PM_NO_ACTION, 0},
- {BTA_DM_PM_NO_ACTION, 0}} /* mode change retry */
- }},
+ /* FTC, OPC, JV : 7 */
+ {(BTA_DM_PM_SNIFF), /* allow sniff */
+ (BTA_DM_PM_SSR2), /* the SSR entry */
+ {
+ {BTA_DM_PM_ACTIVE, 0}, /* conn open active */
+ {BTA_DM_PM_NO_PREF, 0}, /* conn close */
+ {BTA_DM_PM_ACTIVE, 0}, /* app open */
+ {BTA_DM_PM_NO_ACTION, 0}, /* app close */
+ {BTA_DM_PM_NO_ACTION, 0}, /* sco open */
+ {BTA_DM_PM_NO_ACTION, 0}, /* sco close */
+ {BTA_DM_PM_SNIFF_A2DP_IDX,
+ BTA_FTC_IDLE_TO_SNIFF_DELAY_MS}, /* idle */
+ {BTA_DM_PM_ACTIVE, 0}, /* busy */
+ {BTA_DM_PM_NO_ACTION, 0} /* mode change retry */
+ }},
- /* HL : 9 */
- {(BTA_DM_PM_SNIFF), /* allow sniff */
- (BTA_DM_PM_SSR2), /* the SSR entry */
- {
- {{BTA_DM_PM_SNIFF_A2DP_IDX, 5000},
- {BTA_DM_PM_NO_ACTION, 0}}, /* conn open sniff */
- {{BTA_DM_PM_NO_PREF, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* conn close */
- {{BTA_DM_PM_NO_ACTION, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* app open */
- {{BTA_DM_PM_NO_ACTION, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* app close */
- {{BTA_DM_PM_NO_ACTION, 0},
- {BTA_DM_PM_NO_ACTION, 0}}, /* sco open, active */
- {{BTA_DM_PM_NO_ACTION, 0},
- {BTA_DM_PM_NO_ACTION, 0}}, /* sco close sniff */
- {{BTA_DM_PM_NO_ACTION, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* idle */
- {{BTA_DM_PM_ACTIVE, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* busy */
- {{BTA_DM_PM_NO_ACTION, 0},
- {BTA_DM_PM_NO_ACTION, 0}} /* mode change retry */
- }},
+ /* HL : 9 */
+ {(BTA_DM_PM_SNIFF), /* allow sniff */
+ (BTA_DM_PM_SSR2), /* the SSR entry */
+ {
+ {BTA_DM_PM_SNIFF_A2DP_IDX, 5000}, /* conn open sniff */
+ {BTA_DM_PM_NO_PREF, 0}, /* conn close */
+ {BTA_DM_PM_NO_ACTION, 0}, /* app open */
+ {BTA_DM_PM_NO_ACTION, 0}, /* app close */
+ {BTA_DM_PM_NO_ACTION, 0}, /* sco open, active */
+ {BTA_DM_PM_NO_ACTION, 0}, /* sco close sniff */
+ {BTA_DM_PM_NO_ACTION, 0}, /* idle */
+ {BTA_DM_PM_ACTIVE, 0}, /* busy */
+ {BTA_DM_PM_NO_ACTION, 0} /* mode change retry */
+ }},
- /* PANU : 10 */
- {(BTA_DM_PM_SNIFF), /* allow sniff */
- (BTA_DM_PM_SSR2), /* the SSR entry */
- {
- {{BTA_DM_PM_ACTIVE, 0},
- {BTA_DM_PM_NO_ACTION, 0}}, /* conn open active */
- {{BTA_DM_PM_NO_PREF, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* conn close */
- {{BTA_DM_PM_ACTIVE, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* app open */
- {{BTA_DM_PM_NO_ACTION, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* app close */
- {{BTA_DM_PM_NO_ACTION, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* sco open */
- {{BTA_DM_PM_NO_ACTION, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* sco close */
- {{BTA_DM_PM_SNIFF_A2DP_IDX, 5000},
- {BTA_DM_PM_NO_ACTION, 0}}, /* idle */
- {{BTA_DM_PM_ACTIVE, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* busy */
- {{BTA_DM_PM_NO_ACTION, 0},
- {BTA_DM_PM_NO_ACTION, 0}} /* mode change retry */
- }},
+ /* PANU : 10 */
+ {(BTA_DM_PM_SNIFF), /* allow sniff */
+ (BTA_DM_PM_SSR2), /* the SSR entry */
+ {
+ {BTA_DM_PM_ACTIVE, 0}, /* conn open active */
+ {BTA_DM_PM_NO_PREF, 0}, /* conn close */
+ {BTA_DM_PM_ACTIVE, 0}, /* app open */
+ {BTA_DM_PM_NO_ACTION, 0}, /* app close */
+ {BTA_DM_PM_NO_ACTION, 0}, /* sco open */
+ {BTA_DM_PM_NO_ACTION, 0}, /* sco close */
+ {BTA_DM_PM_SNIFF_A2DP_IDX, 5000}, /* idle */
+ {BTA_DM_PM_ACTIVE, 0}, /* busy */
+ {BTA_DM_PM_NO_ACTION, 0} /* mode change retry */
+ }},
- /* NAP : 11 */
- {(BTA_DM_PM_SNIFF), /* allow sniff */
- (BTA_DM_PM_SSR2), /* the SSR entry */
- {
- {{BTA_DM_PM_ACTIVE, 0},
- {BTA_DM_PM_NO_ACTION, 0}}, /* conn open active */
- {{BTA_DM_PM_NO_PREF, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* conn close */
- {{BTA_DM_PM_ACTIVE, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* app open */
- {{BTA_DM_PM_NO_ACTION, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* app close */
- {{BTA_DM_PM_NO_ACTION, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* sco open */
- {{BTA_DM_PM_NO_ACTION, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* sco close */
- {{BTA_DM_PM_SNIFF_A2DP_IDX, 5000},
- {BTA_DM_PM_NO_ACTION, 0}}, /* idle */
- {{BTA_DM_PM_ACTIVE, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* busy */
+ /* NAP : 11 */
+ {(BTA_DM_PM_SNIFF), /* allow sniff */
+ (BTA_DM_PM_SSR2), /* the SSR entry */
+ {
+ {BTA_DM_PM_ACTIVE, 0}, /* conn open active */
+ {BTA_DM_PM_NO_PREF, 0}, /* conn close */
+ {BTA_DM_PM_ACTIVE, 0}, /* app open */
+ {BTA_DM_PM_NO_ACTION, 0}, /* app close */
+ {BTA_DM_PM_NO_ACTION, 0}, /* sco open */
+ {BTA_DM_PM_NO_ACTION, 0}, /* sco close */
+ {BTA_DM_PM_SNIFF_A2DP_IDX, 5000}, /* idle */
+ {BTA_DM_PM_ACTIVE, 0}, /* busy */
+ {BTA_DM_PM_NO_ACTION, 0} /* mode change retry */
+ }},
- {{BTA_DM_PM_NO_ACTION, 0},
- {BTA_DM_PM_NO_ACTION, 0}} /* mode change retry */
- }},
+ /* HS : 12 */
+ {(BTA_DM_PM_SNIFF | BTA_DM_PM_PARK), /* allow park & sniff */
+ (BTA_DM_PM_SSR2), /* the SSR entry */
+ {
+ {BTA_DM_PM_SNIFF, hs_sniff_delay}, /* conn open sniff */
+ {BTA_DM_PM_NO_PREF, 0}, /* conn close */
+ {BTA_DM_PM_NO_ACTION, 0}, /* app open */
+ {BTA_DM_PM_NO_ACTION, 0}, /* app close */
+ {BTA_DM_PM_SNIFF3, 7000}, /* sco open, active */
+ {BTA_DM_PM_SNIFF, 7000}, /* sco close sniff */
+ {BTA_DM_PM_SNIFF, hs_sniff_delay}, /* idle */
+ {BTA_DM_PM_ACTIVE, 0}, /* busy */
+ {BTA_DM_PM_RETRY, 7000} /* mode change retry */
+ }},
- /* HS : 12 */
- {(BTA_DM_PM_SNIFF | BTA_DM_PM_PARK), /* allow park & sniff */
- (BTA_DM_PM_SSR2), /* the SSR entry */
- {
- {{BTA_DM_PM_SNIFF, 7000},
- {BTA_DM_PM_NO_ACTION, 0}}, /* conn open sniff */
- {{BTA_DM_PM_NO_PREF, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* conn close */
- {{BTA_DM_PM_NO_ACTION, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* app open */
- {{BTA_DM_PM_NO_ACTION, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* app close */
- {{BTA_DM_PM_SNIFF3, 7000},
- {BTA_DM_PM_NO_ACTION, 0}}, /* sco open, active */
- {{BTA_DM_PM_SNIFF, 7000},
- {BTA_DM_PM_NO_ACTION, 0}}, /* sco close sniff */
- {{BTA_DM_PM_SNIFF, 7000}, {BTA_DM_PM_NO_ACTION, 0}}, /* idle */
- {{BTA_DM_PM_ACTIVE, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* busy */
- {{BTA_DM_PM_RETRY, 7000},
- {BTA_DM_PM_NO_ACTION, 0}} /* mode change retry */
- }},
+ /* AVK : 13 */
+ {(BTA_DM_PM_SNIFF), /* allow sniff */
+ (BTA_DM_PM_SSR2), /* the SSR entry */
+ {
+ {BTA_DM_PM_SNIFF, 3000}, /* conn open sniff */
+ {BTA_DM_PM_NO_PREF, 0}, /* conn close */
+ {BTA_DM_PM_NO_ACTION, 0}, /* app open */
+ {BTA_DM_PM_NO_ACTION, 0}, /* app close */
+ {BTA_DM_PM_NO_ACTION, 0}, /* sco open */
+ {BTA_DM_PM_NO_ACTION, 0}, /* sco close */
+ {BTA_DM_PM_SNIFF4, 3000}, /* idle */
+ {BTA_DM_PM_ACTIVE, 0}, /* busy */
+ {BTA_DM_PM_NO_ACTION, 0} /* mode change retry */
+ }},
- /* AVK : 13 */
- {(BTA_DM_PM_SNIFF), /* allow sniff */
- (BTA_DM_PM_SSR2), /* the SSR entry */
- {
- {{BTA_DM_PM_SNIFF, 3000},
- {BTA_DM_PM_NO_ACTION, 0}}, /* conn open sniff */
- {{BTA_DM_PM_NO_PREF, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* conn close */
- {{BTA_DM_PM_NO_ACTION, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* app open */
- {{BTA_DM_PM_NO_ACTION, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* app close */
- {{BTA_DM_PM_NO_ACTION, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* sco open */
- {{BTA_DM_PM_NO_ACTION, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* sco close */
- {{BTA_DM_PM_SNIFF4, 3000}, {BTA_DM_PM_NO_ACTION, 0}}, /* idle */
- {{BTA_DM_PM_ACTIVE, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* busy */
- {{BTA_DM_PM_NO_ACTION, 0},
- {BTA_DM_PM_NO_ACTION, 0}} /* mode change retry */
- }}
+ /* GATTC : 14 */
+ {(BTA_DM_PM_SNIFF | BTA_DM_PM_PARK), /* allow park & sniff */
+ (BTA_DM_PM_SSR2), /* the SSR entry */
+ {
+ {BTA_DM_PM_SNIFF_A2DP_IDX, 10000}, /* conn open active */
+ {BTA_DM_PM_NO_PREF, 0}, /* conn close */
+ {BTA_DM_PM_ACTIVE, 0}, /* app open */
+ {BTA_DM_PM_NO_ACTION, 0}, /* app close */
+ {BTA_DM_PM_NO_ACTION, 0}, /* sco open */
+ {BTA_DM_PM_NO_ACTION, 0}, /* sco close */
+ {BTA_DM_PM_SNIFF_A2DP_IDX, 10000}, /* idle */
+ {BTA_DM_PM_ACTIVE, 0}, /* busy */
+ {BTA_DM_PM_RETRY, 5000} /* mode change retry */
+ }},
- /* GATTC : 14 */
- ,
- {(BTA_DM_PM_SNIFF | BTA_DM_PM_PARK), /* allow park & sniff */
- (BTA_DM_PM_SSR2), /* the SSR entry */
- {
- {{BTA_DM_PM_SNIFF_A2DP_IDX, 10000},
- {BTA_DM_PM_NO_ACTION, 0}}, /* conn open active */
- {{BTA_DM_PM_NO_PREF, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* conn close */
- {{BTA_DM_PM_ACTIVE, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* app open */
- {{BTA_DM_PM_NO_ACTION, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* app close */
- {{BTA_DM_PM_NO_ACTION, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* sco open */
- {{BTA_DM_PM_NO_ACTION, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* sco close */
- {{BTA_DM_PM_SNIFF_A2DP_IDX, 10000},
- {BTA_DM_PM_NO_ACTION, 0}}, /* idle */
- {{BTA_DM_PM_ACTIVE, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* busy */
- {{BTA_DM_PM_RETRY, 5000},
- {BTA_DM_PM_NO_ACTION, 0}} /* mode change retry */
- }}
- /* GATTS : 15 */
- ,
- {(BTA_DM_PM_SNIFF | BTA_DM_PM_PARK), /* allow park & sniff */
- (BTA_DM_PM_SSR2), /* the SSR entry */
- {
- {{BTA_DM_PM_NO_PREF, 0},
- {BTA_DM_PM_NO_ACTION, 0}}, /* conn open active */
- {{BTA_DM_PM_NO_PREF, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* conn close */
- {{BTA_DM_PM_NO_PREF, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* app open */
- {{BTA_DM_PM_NO_PREF, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* app close */
- {{BTA_DM_PM_NO_PREF, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* sco open */
- {{BTA_DM_PM_NO_PREF, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* sco close */
- {{BTA_DM_PM_NO_PREF, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* idle */
- {{BTA_DM_PM_NO_PREF, 0}, {BTA_DM_PM_NO_ACTION, 0}}, /* busy */
- {{BTA_DM_PM_RETRY, 5000},
- {BTA_DM_PM_NO_ACTION, 0}} /* mode change retry */
- }}
+ /* GATTS : 15 */
+ {(BTA_DM_PM_SNIFF | BTA_DM_PM_PARK), /* allow park & sniff */
+ (BTA_DM_PM_SSR2), /* the SSR entry */
+ {
+ {BTA_DM_PM_NO_PREF, 0}, /* conn open active */
+ {BTA_DM_PM_NO_PREF, 0}, /* conn close */
+ {BTA_DM_PM_NO_PREF, 0}, /* app open */
+ {BTA_DM_PM_NO_PREF, 0}, /* app close */
+ {BTA_DM_PM_NO_PREF, 0}, /* sco open */
+ {BTA_DM_PM_NO_PREF, 0}, /* sco close */
+ {BTA_DM_PM_NO_PREF, 0}, /* idle */
+ {BTA_DM_PM_NO_PREF, 0}, /* busy */
+ {BTA_DM_PM_RETRY, 5000} /* mode change retry */
+ }}
#ifdef BTE_SIM_APP /* For Insight builds only */
- /* Entries at the end of the pm_spec table are user-defined (runtime
- configurable),
- for power consumption experiments.
- Insight finds the first user-defined entry by looking for the first
- BTA_DM_PM_NO_PREF.
- The number of user_defined specs is defined by
- BTA_SWRAP_UD_PM_SPEC_COUNT */
- ,
- {BTA_DM_PM_NO_PREF}, /* pm_spec USER_DEFINED_0 */
- {BTA_DM_PM_NO_PREF} /* pm_spec USER_DEFINED_1 */
+ /* Entries at the end of the pm_spec table are user-defined (runtime
+ configurable),
+ for power consumption experiments.
+ Insight finds the first user-defined entry by looking for the first
+ BTA_DM_PM_NO_PREF.
+ The number of user_defined specs is defined by
+ BTA_SWRAP_UD_PM_SPEC_COUNT */
+ ,
+ {BTA_DM_PM_NO_PREF}, /* pm_spec USER_DEFINED_0 */
+ {BTA_DM_PM_NO_PREF} /* pm_spec USER_DEFINED_1 */
#endif /* BTE_SIM_APP */
-};
+ };
+ return bta_dm_pm_spec;
+}
/* Please refer to the SNIFF table definitions in bta_api.h.
*
@@ -541,7 +473,6 @@
tBTA_DM_SSR_SPEC* p_bta_dm_ssr_spec = &bta_dm_ssr_spec[0];
const tBTA_DM_PM_CFG* p_bta_dm_pm_cfg = &bta_dm_pm_cfg[0];
-const tBTA_DM_PM_SPEC* p_bta_dm_pm_spec = &bta_dm_pm_spec[0];
const tBTM_PM_PWR_MD* p_bta_dm_pm_md = &bta_dm_pm_md[0];
/* The performance impact of EIR packet size
diff --git a/system/bta/dm/bta_dm_int.h b/system/bta/dm/bta_dm_int.h
index 3eb2baf..a0d6a1f 100644
--- a/system/bta/dm/bta_dm_int.h
+++ b/system/bta/dm/bta_dm_int.h
@@ -451,7 +451,7 @@
typedef struct {
uint8_t allow_mask; /* mask of sniff/hold/park modes to allow */
uint8_t ssr; /* set SSR on conn open/unpark */
- tBTA_DM_PM_ACTN actn_tbl[BTA_DM_PM_NUM_EVTS][2];
+ tBTA_DM_PM_ACTN actn_tbl[BTA_DM_PM_NUM_EVTS];
} tBTA_DM_PM_SPEC;
@@ -470,8 +470,16 @@
extern const uint16_t bta_service_id_to_uuid_lkup_tbl[];
+/* For Insight, PM cfg lookup tables are runtime configurable (to allow tweaking
+ * of params for power consumption measurements) */
+#ifndef BTE_SIM_APP
+#define tBTA_DM_PM_TYPE_QUALIFIER const
+#else
+#define tBTA_DM_PM_TYPE_QUALIFIER
+#endif
+
extern const tBTA_DM_PM_CFG* p_bta_dm_pm_cfg;
-extern const tBTA_DM_PM_SPEC* p_bta_dm_pm_spec;
+tBTA_DM_PM_TYPE_QUALIFIER tBTA_DM_PM_SPEC* get_bta_dm_pm_spec();
extern const tBTM_PM_PWR_MD* p_bta_dm_pm_md;
extern tBTA_DM_SSR_SPEC* p_bta_dm_ssr_spec;
diff --git a/system/bta/dm/bta_dm_pm.cc b/system/bta/dm/bta_dm_pm.cc
index 504799f..6cae974 100644
--- a/system/bta/dm/bta_dm_pm.cc
+++ b/system/bta/dm/bta_dm_pm.cc
@@ -344,7 +344,8 @@
break;
}
- /* if no entries are there for the app_id and subsystem in p_bta_dm_pm_spec*/
+ /* if no entries are there for the app_id and subsystem in
+ * get_bta_dm_pm_spec()*/
if (i > p_bta_dm_pm_cfg[0].app_id) {
LOG_DEBUG("Ignoring power management callback as no service entries exist");
return;
@@ -365,19 +366,19 @@
int index = BTA_DM_PM_SSR0;
if ((BTA_SYS_CONN_OPEN == status) && p_dev &&
(p_dev->Info() & BTA_DM_DI_USE_SSR)) {
- index = p_bta_dm_pm_spec[p_bta_dm_pm_cfg[i].spec_idx].ssr;
+ index = get_bta_dm_pm_spec()[p_bta_dm_pm_cfg[i].spec_idx].ssr;
} else if (BTA_ID_AV == id) {
if (BTA_SYS_CONN_BUSY == status) {
/* set SSR4 for A2DP on SYS CONN BUSY */
index = BTA_DM_PM_SSR4;
} else if (BTA_SYS_CONN_IDLE == status) {
- index = p_bta_dm_pm_spec[p_bta_dm_pm_cfg[i].spec_idx].ssr;
+ index = get_bta_dm_pm_spec()[p_bta_dm_pm_cfg[i].spec_idx].ssr;
}
}
/* if no action for the event */
- if (p_bta_dm_pm_spec[p_bta_dm_pm_cfg[i].spec_idx]
- .actn_tbl[status][0]
+ if (get_bta_dm_pm_spec()[p_bta_dm_pm_cfg[i].spec_idx]
+ .actn_tbl[status]
.power_mode == BTA_DM_PM_NO_ACTION) {
if (BTA_DM_PM_SSR0 == index) /* and do not need to set SSR, return. */
return;
@@ -395,8 +396,8 @@
/* if subsystem has no more preference on the power mode remove
the cb */
- if (p_bta_dm_pm_spec[p_bta_dm_pm_cfg[i].spec_idx]
- .actn_tbl[status][0]
+ if (get_bta_dm_pm_spec()[p_bta_dm_pm_cfg[i].spec_idx]
+ .actn_tbl[status]
.power_mode == BTA_DM_PM_NO_PREF) {
if (j != bta_dm_conn_srvcs.count) {
bta_dm_conn_srvcs.count--;
@@ -499,7 +500,6 @@
const tBTA_DM_PM_CFG* p_pm_cfg;
const tBTA_DM_PM_SPEC* p_pm_spec;
const tBTA_DM_PM_ACTN* p_act0;
- const tBTA_DM_PM_ACTN* p_act1;
tBTA_DM_SRVCS* p_srvcs = NULL;
bool timer_started = false;
uint8_t timer_idx, available_timer = BTA_DM_PM_MODE_TIMER_MAX;
@@ -532,9 +532,8 @@
}
p_pm_cfg = &p_bta_dm_pm_cfg[j];
- p_pm_spec = &p_bta_dm_pm_spec[p_pm_cfg->spec_idx];
- p_act0 = &p_pm_spec->actn_tbl[p_srvcs->state][0];
- p_act1 = &p_pm_spec->actn_tbl[p_srvcs->state][1];
+ p_pm_spec = &get_bta_dm_pm_spec()[p_pm_cfg->spec_idx];
+ p_act0 = &p_pm_spec->actn_tbl[p_srvcs->state];
allowed_modes |= p_pm_spec->allow_mask;
LOG_DEBUG(
@@ -559,15 +558,6 @@
}
}
}
- /* if first preference has already failed, try second preference */
- else if (!(failed_pm & p_act1->power_mode)) {
- pref_modes |= p_act1->power_mode;
-
- if (p_act1->power_mode > pm_action) {
- pm_action = p_act1->power_mode;
- timeout_ms = p_act1->timeout;
- }
- }
}
}
@@ -781,7 +771,7 @@
for (int j = 1; j <= p_bta_dm_pm_cfg[0].app_id; j++) {
/* find the associated p_bta_dm_pm_cfg */
const tBTA_DM_PM_CFG& config = p_bta_dm_pm_cfg[j];
- current_ssr_index = p_bta_dm_pm_spec[config.spec_idx].ssr;
+ current_ssr_index = get_bta_dm_pm_spec()[config.spec_idx].ssr;
if ((config.id == service.id) && ((config.app_id == BTA_ALL_APP_ID) ||
(config.app_id == service.app_id))) {
LOG_INFO("Found connected service:%s app_id:%d peer:%s spec_name:%s",
diff --git a/system/bta/gatt/bta_gattc_act.cc b/system/bta/gatt/bta_gattc_act.cc
index 2068c07..48aa7b9 100644
--- a/system/bta/gatt/bta_gattc_act.cc
+++ b/system/bta/gatt/bta_gattc_act.cc
@@ -630,6 +630,18 @@
}
}
+ if (p_data->hdr.event == BTA_GATTC_INT_DISCONN_EVT) {
+ /* Since link has been disconnected by and it is possible that here are
+ * already some new p_clcb created for the background connect, the number of
+ * p_srcb->num_clcb is NOT 0. This will prevent p_srcb to be cleared inside
+ * the bta_gattc_clcb_dealloc.
+ *
+ * In this point of time, we know that link does not exist, so let's make
+ * sure the connection state, mtu and database is cleared.
+ */
+ bta_gattc_server_disconnected(p_clcb->p_srcb);
+ }
+
bta_gattc_clcb_dealloc(p_clcb);
if (p_data->hdr.event == BTA_GATTC_API_CLOSE_EVT) {
diff --git a/system/bta/gatt/bta_gattc_int.h b/system/bta/gatt/bta_gattc_int.h
index 9e1f761..ef70b83 100644
--- a/system/bta/gatt/bta_gattc_int.h
+++ b/system/bta/gatt/bta_gattc_int.h
@@ -423,6 +423,7 @@
const RawAddress& remote_bda,
tBT_TRANSPORT transport);
extern void bta_gattc_clcb_dealloc(tBTA_GATTC_CLCB* p_clcb);
+extern void bta_gattc_server_disconnected(tBTA_GATTC_SERV* p_srcb);
extern tBTA_GATTC_CLCB* bta_gattc_find_alloc_clcb(tGATT_IF client_if,
const RawAddress& remote_bda,
tBT_TRANSPORT transport);
diff --git a/system/bta/gatt/bta_gattc_utils.cc b/system/bta/gatt/bta_gattc_utils.cc
index a3ff62d..2861873 100644
--- a/system/bta/gatt/bta_gattc_utils.cc
+++ b/system/bta/gatt/bta_gattc_utils.cc
@@ -191,6 +191,26 @@
/*******************************************************************************
*
+ * Function bta_gattc_server_disconnected
+ *
+ * Description Set server cache disconnected
+ *
+ * Returns pointer to the srcb
+ *
+ ******************************************************************************/
+void bta_gattc_server_disconnected(tBTA_GATTC_SERV* p_srcb) {
+ if (p_srcb && p_srcb->connected) {
+ p_srcb->connected = false;
+ p_srcb->state = BTA_GATTC_SERV_IDLE;
+ p_srcb->mtu = 0;
+
+ // clear reallocating
+ p_srcb->gatt_database.Clear();
+ }
+}
+
+/*******************************************************************************
+ *
* Function bta_gattc_clcb_dealloc
*
* Description Deallocte a clcb
diff --git a/system/bta/gatt/bta_gatts_api.cc b/system/bta/gatt/bta_gatts_api.cc
index 0de36db..d0e4f44 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));
diff --git a/system/bta/include/bta_api.h b/system/bta/include/bta_api.h
index f5caac3..2efb326 100644
--- a/system/bta/include/bta_api.h
+++ b/system/bta/include/bta_api.h
@@ -1224,4 +1224,14 @@
******************************************************************************/
extern void BTA_DmBleResetId(void);
+/*******************************************************************************
+ *
+ * Function BTA_DmCheckLeAudioCapable
+ *
+ * Description Checks if device should be considered as LE Audio capable
+ *
+ * Returns True if Le Audio capable device, false otherwise
+ *
+ ******************************************************************************/
+extern bool BTA_DmCheckLeAudioCapable(const RawAddress& address);
#endif /* BTA_API_H */
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 5ad4163..b5061ef 100644
--- a/system/bta/le_audio/client.cc
+++ b/system/bta/le_audio/client.cc
@@ -1108,13 +1108,12 @@
if (!leAudioDevice) {
leAudioDevices_.Add(address, DeviceConnectState::CONNECTING_BY_USER);
} else {
- if (leAudioDevice->GetConnectionState() !=
- DeviceConnectState::DISCONNECTED) {
- LOG_ERROR(
- "Device %s is in invalid state: %s",
- leAudioDevice->address_.ToString().c_str(),
- bluetooth::common::ToString(leAudioDevice->GetConnectionState())
- .c_str());
+ auto current_connect_state = leAudioDevice->GetConnectionState();
+ if ((current_connect_state == DeviceConnectState::CONNECTED) ||
+ (current_connect_state == DeviceConnectState::CONNECTING_BY_USER)) {
+ LOG_ERROR("Device %s is in invalid state: %s",
+ leAudioDevice->address_.ToString().c_str(),
+ bluetooth::common::ToString(current_connect_state).c_str());
return;
}
@@ -4414,7 +4413,7 @@
void le_audio_gattc_callback(tBTA_GATTC_EVT event, tBTA_GATTC* p_data) {
if (!p_data || !instance) return;
- DLOG(INFO) << __func__ << " event = " << +event;
+ LOG_DEBUG("event = %d", static_cast<int>(event));
switch (event) {
case BTA_GATTC_DEREG_EVT:
diff --git a/system/bta/le_audio/devices.cc b/system/bta/le_audio/devices.cc
index f40c282..16c79b5 100644
--- a/system/bta/le_audio/devices.cc
+++ b/system/bta/le_audio/devices.cc
@@ -2469,7 +2469,8 @@
<< ",\tactive: " << ase.active << ", dir: "
<< (ase.direction == types::kLeAudioDirectionSink ? "sink"
: "source")
- << ",\tcis_id: " << static_cast<int>(ase.cis_id) << ",\tstate: "
+ << ",\tcis_id: " << static_cast<int>(ase.cis_id)
+ << ",\tcis_handle: " << ase.cis_conn_hdl << ",\tstate: "
<< bluetooth::common::ToString(ase.data_path_state);
}
}
@@ -2546,13 +2547,19 @@
void LeAudioDevice::DeactivateAllAses(void) {
for (auto& ase : ases_) {
- if (ase.active) {
- ase.state = AseState::BTA_LE_AUDIO_ASE_STATE_IDLE;
- ase.data_path_state = AudioStreamDataPathState::IDLE;
- ase.active = false;
- ase.cis_id = le_audio::kInvalidCisId;
- ase.cis_conn_hdl = 0;
+ if (ase.active == false &&
+ ase.data_path_state != AudioStreamDataPathState::IDLE) {
+ LOG_WARN(
+ " %s, ase_id: %d, ase.cis_id: %d, cis_handle: 0x%02x, "
+ "ase.data_path=%s",
+ address_.ToString().c_str(), ase.id, ase.cis_id, ase.cis_conn_hdl,
+ bluetooth::common::ToString(ase.data_path_state).c_str());
}
+ ase.state = AseState::BTA_LE_AUDIO_ASE_STATE_IDLE;
+ ase.data_path_state = AudioStreamDataPathState::IDLE;
+ ase.active = false;
+ ase.cis_id = le_audio::kInvalidCisId;
+ ase.cis_conn_hdl = 0;
}
}
diff --git a/system/bta/le_audio/state_machine.cc b/system/bta/le_audio/state_machine.cc
index 8476fec..09bba6f 100644
--- a/system/bta/le_audio/state_machine.cc
+++ b/system/bta/le_audio/state_machine.cc
@@ -614,6 +614,8 @@
LeAudioDevice* leAudioDevice) {
FreeLinkQualityReports(leAudioDevice);
leAudioDevice->conn_id_ = GATT_INVALID_CONN_ID;
+ /* mark ASEs as not used. */
+ leAudioDevice->DeactivateAllAses();
if (!group) {
LOG(ERROR) << __func__
@@ -622,9 +624,6 @@
return;
}
- /* mark ASEs as not used. */
- leAudioDevice->DeactivateAllAses();
-
/* If group is in Idle and not transitioning, just update the current group
* audio context availability which could change due to disconnected group
* member.
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/co/bta_hh_co.cc b/system/btif/co/bta_hh_co.cc
index 90fe1df..6115627 100644
--- a/system/btif/co/bta_hh_co.cc
+++ b/system/btif/co/bta_hh_co.cc
@@ -673,15 +673,16 @@
ev.type = UHID_FEATURE_ANSWER;
ev.u.feature_answer.id = *get_rpt_id;
ev.u.feature_answer.err = status;
- ev.u.feature_answer.size = len;
+ ev.u.feature_answer.size = len - GET_RPT_RSP_OFFSET;
osi_free(get_rpt_id);
- if (len > 0) {
- if (len > UHID_DATA_MAX) {
+ if (len > GET_RPT_RSP_OFFSET) {
+ if (len - GET_RPT_RSP_OFFSET > UHID_DATA_MAX) {
APPL_TRACE_WARNING("%s: Report size greater than allowed size",
__func__);
return;
}
- memcpy(ev.u.feature_answer.data, p_rpt + GET_RPT_RSP_OFFSET, len);
+ memcpy(ev.u.feature_answer.data, p_rpt + GET_RPT_RSP_OFFSET,
+ len - GET_RPT_RSP_OFFSET);
uhid_write(p_dev->fd, &ev);
}
}
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/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..ed0e952 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>
@@ -500,7 +501,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;
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_dm.cc b/system/btif/src/btif_dm.cc
index b05c342..733e597 100644
--- a/system/btif/src/btif_dm.cc
+++ b/system/btif/src/btif_dm.cc
@@ -680,12 +680,16 @@
* up LE profile connection, and limits all possible service discovery
* ordering issues (first Classic, GATT over SDP, etc) */
static bool is_device_le_audio_capable(const RawAddress bd_addr) {
- if (!LeAudioClient::IsLeAudioClientRunning() ||
- !check_cod_le_audio(bd_addr)) {
+ if (!LeAudioClient::IsLeAudioClientRunning()) {
/* If LE Audio profile is not enabled, do nothing. */
return false;
}
+ if (!check_cod_le_audio(bd_addr) && !BTA_DmCheckLeAudioCapable(bd_addr)) {
+ /* LE Audio not present in CoD or in LE Advertisement, do nothing.*/
+ return false;
+ }
+
tBT_DEVICE_TYPE tmp_dev_type;
tBLE_ADDR_TYPE addr_type = BLE_ADDR_PUBLIC;
BTM_ReadDevInfo(bd_addr, &tmp_dev_type, &addr_type);
@@ -1377,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);
}
@@ -1517,12 +1532,12 @@
* before before passing services to upper layers. */
if ((bd_addr == pairing_cb.bd_addr ||
bd_addr == pairing_cb.static_bdaddr) &&
- a2dp_sink_capable &&
- LeAudioClient::IsLeAudioClientRunning() &&
+ a2dp_sink_capable && LeAudioClient::IsLeAudioClientRunning() &&
pairing_cb.gatt_over_le !=
btif_dm_pairing_cb_t::ServiceDiscoveryState::FINISHED &&
(check_cod_le_audio(bd_addr) ||
- metadata_cb.le_audio_cache.contains(bd_addr))) {
+ metadata_cb.le_audio_cache.contains(bd_addr) ||
+ BTA_DmCheckLeAudioCapable(bd_addr))) {
skip_reporting_wait_for_le = true;
}
diff --git a/system/btif/src/btif_rc.cc b/system/btif/src/btif_rc.cc
index d66c427..1b68326 100644
--- a/system/btif/src/btif_rc.cc
+++ b/system/btif/src/btif_rc.cc
@@ -3650,29 +3650,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 +3684,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 +3730,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 +3755,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 +3818,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/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/hci/acl_manager/round_robin_scheduler_test.cc b/system/gd/hci/acl_manager/round_robin_scheduler_test.cc
index 9d43c0f..e27ff7c 100644
--- a/system/gd/hci/acl_manager/round_robin_scheduler_test.cc
+++ b/system/gd/hci/acl_manager/round_robin_scheduler_test.cc
@@ -136,8 +136,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;
}
}
diff --git a/system/gd/hci/le_advertising_manager.cc b/system/gd/hci/le_advertising_manager.cc
index 9953ab8..c96b971 100644
--- a/system/gd/hci/le_advertising_manager.cc
+++ b/system/gd/hci/le_advertising_manager.cc
@@ -61,7 +61,6 @@
int8_t tx_power;
uint16_t duration;
uint8_t max_extended_advertising_events;
- bool pending_start = false; // whether we have started but are still in the queue
bool started = false;
bool connectable = false;
bool directed = false;
@@ -348,7 +347,6 @@
enable_advertiser(id, true, 0, 0);
} else {
enabled_sets_[id].advertising_handle_ = id;
- advertising_sets_[id].pending_start = true;
}
} break;
case (AdvertisingApiType::ANDROID_HCI): {
@@ -374,7 +372,6 @@
enable_advertiser(id, true, 0, 0);
} else {
enabled_sets_[id].advertising_handle_ = id;
- advertising_sets_[id].pending_start = true;
}
} break;
case (AdvertisingApiType::EXTENDED): {
@@ -485,9 +482,6 @@
if (!paused) {
enable_advertiser(id, true, duration, max_ext_adv_events);
} else {
- // invoke callbacks upon OnResume()
- advertising_sets_[id].pending_start = true;
-
EnabledSet curr_set;
curr_set.advertising_handle_ = id;
curr_set.duration_ = duration;
@@ -927,11 +921,6 @@
std::vector<EnabledSet> enabled_sets = {curr_set};
Enable enable_value = enable ? Enable::ENABLED : Enable::DISABLED;
- if (enable) {
- // so that callbacks get invoked on command complete
- advertising_sets_[advertiser_id].pending_start = true;
- }
-
switch (advertising_api_type_) {
case (AdvertisingApiType::LEGACY): {
le_advertising_interface_->EnqueueCommand(
@@ -940,22 +929,29 @@
this,
&impl::on_set_advertising_enable_complete<LeSetAdvertisingEnableCompleteView>,
enable,
- enabled_sets));
+ enabled_sets,
+ true /* trigger callbacks */));
} break;
case (AdvertisingApiType::ANDROID_HCI): {
le_advertising_interface_->EnqueueCommand(
hci::LeMultiAdvtSetEnableBuilder::Create(enable_value, advertiser_id),
module_handler_->BindOnceOn(
- this, &impl::on_set_advertising_enable_complete<LeMultiAdvtCompleteView>, enable, enabled_sets));
+ this,
+ &impl::on_set_advertising_enable_complete<LeMultiAdvtCompleteView>,
+ enable,
+ enabled_sets,
+ true /* trigger callbacks */));
} break;
case (AdvertisingApiType::EXTENDED): {
le_advertising_interface_->EnqueueCommand(
hci::LeSetExtendedAdvertisingEnableBuilder::Create(enable_value, enabled_sets),
module_handler_->BindOnceOn(
this,
- &impl::on_set_extended_advertising_enable_complete<LeSetExtendedAdvertisingEnableCompleteView>,
+ &impl::on_set_extended_advertising_enable_complete<
+ LeSetExtendedAdvertisingEnableCompleteView>,
enable,
- enabled_sets));
+ enabled_sets,
+ true /* trigger callbacks */));
} break;
}
@@ -1121,13 +1117,17 @@
case (AdvertisingApiType::LEGACY): {
le_advertising_interface_->EnqueueCommand(
hci::LeSetAdvertisingEnableBuilder::Create(Enable::ENABLED),
- common::init_flags::trigger_advertising_callbacks_on_first_resume_after_pause_is_enabled()
+ common::init_flags::
+ trigger_advertising_callbacks_on_first_resume_after_pause_is_enabled()
? module_handler_->BindOnceOn(
this,
- &impl::on_set_advertising_enable_complete<LeSetAdvertisingEnableCompleteView>,
+ &impl::on_set_advertising_enable_complete<
+ LeSetAdvertisingEnableCompleteView>,
true,
- enabled_sets)
- : module_handler_->BindOnce(impl::check_status<LeSetAdvertisingEnableCompleteView>));
+ enabled_sets,
+ false /* trigger_callbacks */)
+ : module_handler_->BindOnce(
+ impl::check_status<LeSetAdvertisingEnableCompleteView>));
} break;
case (AdvertisingApiType::ANDROID_HCI): {
for (size_t i = 0; i < enabled_sets_.size(); i++) {
@@ -1135,12 +1135,14 @@
if (id != kInvalidHandle) {
le_advertising_interface_->EnqueueCommand(
hci::LeMultiAdvtSetEnableBuilder::Create(Enable::ENABLED, id),
- common::init_flags::trigger_advertising_callbacks_on_first_resume_after_pause_is_enabled()
+ common::init_flags::
+ trigger_advertising_callbacks_on_first_resume_after_pause_is_enabled()
? module_handler_->BindOnceOn(
this,
&impl::on_set_advertising_enable_complete<LeMultiAdvtCompleteView>,
true,
- enabled_sets)
+ enabled_sets,
+ false /* trigger_callbacks */)
: module_handler_->BindOnce(impl::check_status<LeMultiAdvtCompleteView>));
}
}
@@ -1149,14 +1151,17 @@
if (enabled_sets.size() != 0) {
le_advertising_interface_->EnqueueCommand(
hci::LeSetExtendedAdvertisingEnableBuilder::Create(Enable::ENABLED, enabled_sets),
- common::init_flags::trigger_advertising_callbacks_on_first_resume_after_pause_is_enabled()
+ common::init_flags::
+ trigger_advertising_callbacks_on_first_resume_after_pause_is_enabled()
? module_handler_->BindOnceOn(
this,
&impl::on_set_extended_advertising_enable_complete<
LeSetExtendedAdvertisingEnableCompleteView>,
true,
- enabled_sets)
- : module_handler_->BindOnce(impl::check_status<LeSetExtendedAdvertisingEnableCompleteView>));
+ enabled_sets,
+ false /* trigger_callbacks */)
+ : module_handler_->BindOnce(
+ impl::check_status<LeSetExtendedAdvertisingEnableCompleteView>));
}
} break;
}
@@ -1223,7 +1228,11 @@
}
template <class View>
- void on_set_advertising_enable_complete(bool enable, std::vector<EnabledSet> enabled_sets, CommandCompleteView view) {
+ void on_set_advertising_enable_complete(
+ bool enable,
+ std::vector<EnabledSet> enabled_sets,
+ bool trigger_callbacks,
+ CommandCompleteView view) {
ASSERT(view.IsValid());
auto complete_view = View::Create(view);
ASSERT(complete_view.IsValid());
@@ -1252,15 +1261,9 @@
}
if (started) {
- // This event can be triggered from OnPause / OnResume. If so, only invoke callbacks if we were initially paused
- // (pending_start) after an API invocation (i.e. StartAdvertising -> currently paused -> OnResume -> enabled
- // [callback], but StartAdvertising -> enabled [callback] -> resumed -> OnPause -> OnResume [NO callback]
- if (!advertising_sets_[enabled_set.advertising_handle_].pending_start) {
- continue;
+ if (trigger_callbacks) {
+ advertising_callbacks_->OnAdvertisingEnabled(id, enable, advertising_status);
}
- advertising_callbacks_->OnAdvertisingEnabled(id, enable, advertising_status);
- // since we have started, we are no longer pending
- advertising_sets_[enabled_set.advertising_handle_].pending_start = false;
} else {
advertising_sets_[enabled_set.advertising_handle_].started = true;
advertising_callbacks_->OnAdvertisingSetStarted(reg_id, id, le_physical_channel_tx_power_, advertising_status);
@@ -1270,7 +1273,10 @@
template <class View>
void on_set_extended_advertising_enable_complete(
- bool enable, std::vector<EnabledSet> enabled_sets, CommandCompleteView view) {
+ bool enable,
+ std::vector<EnabledSet> enabled_sets,
+ bool trigger_callbacks,
+ CommandCompleteView view) {
ASSERT(view.IsValid());
auto complete_view = LeSetExtendedAdvertisingEnableCompleteView::Create(view);
ASSERT(complete_view.IsValid());
@@ -1302,15 +1308,9 @@
}
if (started) {
- // This event can be triggered from OnPause / OnResume. If so, only invoke callbacks if we were initially paused
- // (pending_start) after an API invocation (i.e. StartAdvertising -> currently paused -> OnResume -> enabled
- // [callback], but StartAdvertising -> enabled [callback] -> resumed -> OnPause -> OnResume [NO callback]
- if (!advertising_sets_[enabled_set.advertising_handle_].pending_start) {
- continue;
+ if (trigger_callbacks) {
+ advertising_callbacks_->OnAdvertisingEnabled(id, enable, advertising_status);
}
- advertising_callbacks_->OnAdvertisingEnabled(id, enable, advertising_status);
- // since we have started, we are no longer pending
- advertising_sets_[enabled_set.advertising_handle_].pending_start = false;
} else {
advertising_sets_[enabled_set.advertising_handle_].started = true;
advertising_callbacks_->OnAdvertisingSetStarted(reg_id, id, tx_power, advertising_status);
diff --git a/system/gd/hci/le_advertising_manager_test.cc b/system/gd/hci/le_advertising_manager_test.cc
index 6b10971..c7621c9 100644
--- a/system/gd/hci/le_advertising_manager_test.cc
+++ b/system/gd/hci/le_advertising_manager_test.cc
@@ -36,8 +36,13 @@
namespace hci {
namespace {
+using namespace std::literals;
+
using packet::RawBuilder;
+using testing::_;
+using testing::InSequence;
+
class TestController : public Controller {
public:
bool IsSupported(OpCode op_code) const override {
@@ -228,11 +233,7 @@
void on_set_terminated(ErrorCode error_code, uint8_t, uint8_t) {}
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(2s));
}
class MockAdvertisingCallback : public AdvertisingCallback {
@@ -1003,6 +1004,47 @@
test_hci_layer_->IncomingEvent(LeSetExtendedAdvertisingEnableCompleteBuilder::Create(uint8_t{1}, ErrorCode::SUCCESS));
}
+TEST_F(LeExtendedAdvertisingAPITest, disable_after_enable) {
+ // we expect Started -> Enable(false) -> Enable(true) -> Enable(false)
+
+ // setup already arranges everything and starts the advertiser
+
+ // expect
+ InSequence s;
+ EXPECT_CALL(mock_advertising_callback_, OnAdvertisingEnabled(_, false, _));
+ EXPECT_CALL(mock_advertising_callback_, OnAdvertisingEnabled(_, true, _));
+ EXPECT_CALL(mock_advertising_callback_, OnAdvertisingEnabled(_, false, _));
+ EXPECT_CALL(mock_advertising_callback_, OnAdvertisingEnabled(_, true, _));
+
+ // act
+
+ // disable
+ le_advertising_manager_->EnableAdvertiser(advertiser_id_, false, 0x00, 0x00);
+ test_hci_layer_->GetCommand();
+ test_hci_layer_->IncomingEvent(
+ LeSetExtendedAdvertisingEnableCompleteBuilder::Create(uint8_t{1}, ErrorCode::SUCCESS));
+
+ // enable
+ le_advertising_manager_->EnableAdvertiser(advertiser_id_, true, 0x00, 0x00);
+ test_hci_layer_->GetCommand();
+ test_hci_layer_->IncomingEvent(
+ LeSetExtendedAdvertisingEnableCompleteBuilder::Create(uint8_t{1}, ErrorCode::SUCCESS));
+
+ // disable
+ le_advertising_manager_->EnableAdvertiser(advertiser_id_, false, 0x00, 0x00);
+ test_hci_layer_->GetCommand();
+ test_hci_layer_->IncomingEvent(
+ LeSetExtendedAdvertisingEnableCompleteBuilder::Create(uint8_t{1}, ErrorCode::SUCCESS));
+
+ // enable
+ le_advertising_manager_->EnableAdvertiser(advertiser_id_, true, 0x00, 0x00);
+ test_hci_layer_->GetCommand();
+ test_hci_layer_->IncomingEvent(
+ LeSetExtendedAdvertisingEnableCompleteBuilder::Create(uint8_t{1}, ErrorCode::SUCCESS));
+
+ sync_client_handler();
+}
+
TEST_F(LeExtendedAdvertisingAPITest, set_periodic_parameter) {
PeriodicAdvertisingParameters advertising_config{};
advertising_config.max_interval = 0x1000;
@@ -1132,7 +1174,6 @@
[](std::promise<uint8_t> promise, uint8_t id, uint8_t _status) { promise.set_value(id); },
std::move(id_promise)));
sync_client_handler();
- LOG_INFO("start");
auto set_id = id_future.get();
auto status_promise = std::promise<ErrorCode>{};
@@ -1175,6 +1216,46 @@
// assert
EXPECT_EQ(status_future.get(), ErrorCode::SUCCESS);
+
+ sync_client_handler();
+}
+
+TEST_F(LeExtendedAdvertisingAPITest, no_callbacks_on_pause) {
+ // arrange
+ auto test_le_address_manager = (TestLeAddressManager*)test_acl_manager_->GetLeAddressManager();
+
+ // expect
+ EXPECT_CALL(mock_advertising_callback_, OnAdvertisingEnabled(_, _, _)).Times(0);
+
+ // act
+ LOG_INFO("pause");
+ test_le_address_manager->client_->OnPause();
+ test_hci_layer_->GetCommand();
+ test_hci_layer_->IncomingEvent(
+ LeSetExtendedAdvertisingEnableCompleteBuilder::Create(1, ErrorCode::SUCCESS));
+
+ sync_client_handler();
+}
+
+TEST_F(LeExtendedAdvertisingAPITest, no_callbacks_on_resume) {
+ // arrange
+ auto test_le_address_manager = (TestLeAddressManager*)test_acl_manager_->GetLeAddressManager();
+ test_le_address_manager->client_->OnPause();
+ test_hci_layer_->GetCommand();
+ test_hci_layer_->IncomingEvent(
+ LeSetExtendedAdvertisingEnableCompleteBuilder::Create(1, ErrorCode::SUCCESS));
+ sync_client_handler();
+
+ // expect
+ EXPECT_CALL(mock_advertising_callback_, OnAdvertisingEnabled(_, _, _)).Times(0);
+
+ // act
+ test_le_address_manager->client_->OnResume();
+ test_hci_layer_->GetCommand();
+ test_hci_layer_->IncomingEvent(
+ LeSetExtendedAdvertisingEnableCompleteBuilder::Create(1, ErrorCode::SUCCESS));
+
+ sync_client_handler();
}
} // namespace
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/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..9bc8dda 100644
--- a/system/stack/Android.bp
+++ b/system/stack/Android.bp
@@ -978,6 +978,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",
],
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 0d49c41..c6264dd 100644
--- a/system/stack/acl/btm_acl.cc
+++ b/system/stack/acl/btm_acl.cc
@@ -630,20 +630,6 @@
return;
}
- /* if we are trying to drop encryption on an encrypted connection, drop the
- * connection */
- 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 */
@@ -1768,7 +1754,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;
@@ -1777,6 +1763,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) {
@@ -1784,6 +1774,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);
@@ -1792,6 +1787,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;
}
@@ -1805,6 +1804,11 @@
(*p_cb)(&result);
}
+
+ return;
+
+ err_out:
+ LOG_ERROR("Bogus event packet, too short");
}
/*******************************************************************************
@@ -1834,7 +1838,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;
@@ -1843,11 +1847,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);
@@ -1863,6 +1875,11 @@
}
(*p_cb)(&result);
}
+
+ return;
+
+err_out:
+ LOG_ERROR("Bogus event packet, too short");
}
/*******************************************************************************
@@ -1997,7 +2014,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;
@@ -2006,12 +2023,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);
@@ -2030,6 +2055,11 @@
(*p_cb)(&result);
}
+
+ return;
+
+err_out:
+ LOG_ERROR("Bogus Link Quality event packet, size: %d", evt_len);
}
/*******************************************************************************
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..2c77945 100644
--- a/system/stack/btm/btm_ble.cc
+++ b/system/stack/btm/btm_ble.cc
@@ -1019,7 +1019,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 +1031,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 +1047,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__);
}
/*******************************************************************************
diff --git a/system/stack/btm/btm_ble_gap.cc b/system/stack/btm/btm_ble_gap.cc
index 5a5fd84..5aa30e7 100644
--- a/system/stack/btm/btm_ble_gap.cc
+++ b/system/stack/btm/btm_ble_gap.cc
@@ -2393,9 +2393,7 @@
has_advertising_flags = true;
p_cur->flag = *p_flag;
}
- }
- if (!data.empty()) {
/* Check to see the BLE device has the Appearance UUID in the advertising
* data. If it does
* then try to convert the appearance value to a class of device value
@@ -2425,14 +2423,31 @@
}
}
}
- }
- if (!data.empty()) {
const uint8_t* p_rsi =
AdvertiseDataParser::GetFieldByType(data, BTM_BLE_AD_TYPE_RSI, &len);
if (p_rsi != nullptr && len == 6) {
STREAM_TO_BDADDR(p_cur->ble_ad_rsi, p_rsi);
}
+
+ const uint8_t* p_service_data = data.data();
+ uint8_t service_data_len = 0;
+
+ while ((p_service_data = AdvertiseDataParser::GetFieldByType(
+ p_service_data + 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);
+
+ if (uuid == 0x184E /* Audio Stream Control service */ ||
+ uuid == 0x184F /* Broadcast Audio Scan service */ ||
+ uuid == 0x1850 /* Published Audio Capabilities service */ ||
+ uuid == 0x1853 /* Common Audio service */) {
+ p_cur->ble_ad_is_le_audio_capable = true;
+ break;
+ }
+ }
}
// Non-connectable packets may omit flags entirely, in which case nothing
@@ -3181,9 +3196,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
@@ -3198,6 +3218,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");
@@ -3206,6 +3232,10 @@
}
btsnd_hcic_rmt_ver_req(handle);
+ return;
+
+err_out:
+ LOG_ERROR("bogus event packet, too short");
}
/*******************************************************************************
@@ -3217,11 +3247,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_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..0abe21f 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,6 +355,12 @@
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) {
@@ -323,6 +378,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 +406,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 +422,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 +562,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 +585,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 +605,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 +958,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 efea695..8df08bb 100644
--- a/system/stack/btm/btm_sec.cc
+++ b/system/stack/btm/btm_sec.cc
@@ -2046,8 +2046,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 +2992,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 +3019,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__);
}
/*******************************************************************************
@@ -3750,7 +3771,7 @@
void btm_sec_disconnected(uint16_t handle, tHCI_REASON reason,
std::string comment) {
if ((reason != HCI_ERR_CONN_CAUSE_LOCAL_HOST) &&
- (reason != HCI_ERR_PEER_USER)) {
+ (reason != HCI_ERR_PEER_USER) && (reason != HCI_ERR_REMOTE_POWER_OFF)) {
LOG_WARN("Got uncommon disconnection reason:%s handle:0x%04x comment:%s",
hci_error_code_text(reason).c_str(), handle, comment.c_str());
}
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/btm/neighbor_inquiry.h b/system/stack/btm/neighbor_inquiry.h
index 20f76d4..d03ea08 100644
--- a/system/stack/btm/neighbor_inquiry.h
+++ b/system/stack/btm/neighbor_inquiry.h
@@ -117,6 +117,7 @@
int8_t ble_tx_power;
uint16_t ble_periodic_adv_int;
RawAddress ble_ad_rsi; /* Resolvable Set Identifier from advertising */
+ bool ble_ad_is_le_audio_capable;
uint8_t flag;
bool include_rsi;
RawAddress original_bda;
diff --git a/system/stack/btu/btu_hcif.cc b/system/stack/btu/btu_hcif.cc
index 3c0f5d0..552b174 100644
--- a/system/stack/btu/btu_hcif.cc
+++ b/system/stack/btu/btu_hcif.cc
@@ -96,7 +96,7 @@
static void btu_ble_proc_ltk_req(uint8_t* p);
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);
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);
@@ -1687,10 +1692,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..f0d8254 100644
--- a/system/stack/eatt/eatt_impl.h
+++ b/system/stack/eatt/eatt_impl.h
@@ -874,7 +874,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/gatt/connection_manager.cc b/system/stack/gatt/connection_manager.cc
index f15be22..5c2e880 100644
--- a/system/stack/gatt/connection_manager.cc
+++ b/system/stack/gatt/connection_manager.cc
@@ -42,6 +42,8 @@
#define DIRECT_CONNECT_TIMEOUT (30 * 1000) /* 30 seconds */
+constexpr char kBtmLogTag[] = "TA";
+
struct closure_data {
base::OnceClosure user_task;
base::Location posted_from;
@@ -119,12 +121,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;
@@ -182,6 +183,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());
@@ -192,6 +195,9 @@
void target_announcements_filtering_set(bool enable) {
LOG_DEBUG("enable %d", enable);
+ BTM_LogHistory(kBtmLogTag, RawAddress::kEmpty,
+ (enable ? "Start filtering" : "Stop filtering"));
+
/* Safe to call as if there is no support for filtering, this call will be
* ignored. */
bluetooth::shim::set_target_announcements_filter(enable);
@@ -247,6 +253,10 @@
}
bgconn_dev[address].doing_targeted_announcements_conn.insert(app_id);
+ if (bgconn_dev[address].doing_targeted_announcements_conn.size() == 1) {
+ BTM_LogHistory(kBtmLogTag, address, "Allow connection from");
+ }
+
if (num_of_targeted_announcements_users() == 1) {
target_announcements_filtering_set(true);
}
@@ -336,13 +346,20 @@
auto num_of_targeted_announcements_before_remove =
it->second.doing_targeted_announcements_conn.size();
- if (!it->second.doing_bg_conn.erase(app_id) &&
- !it->second.doing_targeted_announcements_conn.erase(app_id)) {
+ bool removed_from_bg_conn = (it->second.doing_bg_conn.erase(app_id) > 0);
+ bool removed_from_ta =
+ (it->second.doing_targeted_announcements_conn.erase(app_id) > 0);
+ if (!removed_from_bg_conn && !removed_from_ta) {
LOG_WARN("Failed to remove background connection app %d for address %s",
static_cast<int>(app_id), address.ToString().c_str());
return false;
}
+ if (removed_from_ta &&
+ it->second.doing_targeted_announcements_conn.size() == 0) {
+ BTM_LogHistory(kBtmLogTag, address, "Ignore connection from");
+ }
+
if (is_anyone_connecting(it)) {
LOG_DEBUG("some device is still connecting, app_id=%d, address=%s",
static_cast<int>(app_id), address.ToString().c_str());
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_utils.cc b/system/stack/gatt/gatt_utils.cc
index ec19948..cd5099c 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;
+ }
}
}
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/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 5115142..05d4115 100644
--- a/system/stack/include/gatt_api.h
+++ b/system/stack/include/gatt_api.h
@@ -213,6 +213,8 @@
GATT_CONN_FAILED_ESTABLISHMENT = HCI_ERR_CONN_FAILED_ESTABLISHMENT,
+ GATT_CONN_TERMINATED_POWER_OFF = HCI_ERR_REMOTE_POWER_OFF,
+
BTA_GATT_CONN_NONE = 0x0101, /* 0x0101 no connection to cancel */
} tGATT_DISCONN_REASON;
@@ -232,6 +234,7 @@
CASE_RETURN_TEXT(GATT_CONN_LMP_TIMEOUT);
CASE_RETURN_TEXT(GATT_CONN_FAILED_ESTABLISHMENT);
CASE_RETURN_TEXT(BTA_GATT_CONN_NONE);
+ CASE_RETURN_TEXT(GATT_CONN_TERMINATED_POWER_OFF);
default:
return base::StringPrintf("UNKNOWN[%hu]", reason);
}
diff --git a/system/stack/include/hci_error_code.h b/system/stack/include/hci_error_code.h
index bbff6a6..c2abb8d 100644
--- a/system/stack/include/hci_error_code.h
+++ b/system/stack/include/hci_error_code.h
@@ -44,6 +44,7 @@
HCI_ERR_HOST_TIMEOUT = 0x10, // stack/btm/btm_ble_gap,
HCI_ERR_ILLEGAL_PARAMETER_FMT = 0x12,
HCI_ERR_PEER_USER = 0x13,
+ HCI_ERR_REMOTE_POWER_OFF = 0x15,
HCI_ERR_CONN_CAUSE_LOCAL_HOST = 0x16,
HCI_ERR_REPEATED_ATTEMPTS = 0x17,
HCI_ERR_PAIRING_NOT_ALLOWED = 0x18,
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/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/test/a2dp/AndroidTest.xml b/system/stack/test/a2dp/AndroidTest.xml
index 4f81751..71fc46c 100644
--- a/system/stack/test/a2dp/AndroidTest.xml
+++ b/system/stack/test/a2dp/AndroidTest.xml
@@ -34,6 +34,7 @@
<!-- Only run tests in MTS if the Bluetooth Mainline module is installed. -->
<object type="module_controller"
class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
- <option name="mainline-module-package-name" value="com.google.android.bluetooth" />
+ <option name="mainline-module-package-name" value="com.android.btservices" />
+ <option name="mainline-module-package-name" value="com.google.android.btservices" />
</object>
</configuration>
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_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/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_connection_manager_test.cc b/system/stack/test/gatt_connection_manager_test.cc
index 194d8fc..a073551 100644
--- a/system/stack/test/gatt_connection_manager_test.cc
+++ b/system/stack/test/gatt_connection_manager_test.cc
@@ -78,6 +78,9 @@
localAcceptlistMock->EnableTargetedAnnouncements(enable, p_results_cb);
}
+void BTM_LogHistory(const std::string& tag, const RawAddress& bd_addr,
+ const std::string& msg){};
+
namespace bluetooth {
namespace shim {
bool is_gd_l2cap_enabled() { return false; }
diff --git a/system/stack/test/rfcomm/stack_rfcomm_test.cc b/system/stack/test/rfcomm/stack_rfcomm_test.cc
index f0812ce..af8a30d 100644
--- a/system/stack/test/rfcomm/stack_rfcomm_test.cc
+++ b/system/stack/test/rfcomm/stack_rfcomm_test.cc
@@ -474,7 +474,7 @@
tL2CAP_APPL_INFO l2cap_appl_info_;
};
-TEST_F(StackRfcommTest, SingleServerConnectionHelloWorld) {
+TEST_F(StackRfcommTest, DISABLED_SingleServerConnectionHelloWorld) {
// Prepare a server channel at kTestChannelNumber0
static const uint16_t acl_handle = 0x0009;
static const uint16_t lcid = 0x0054;
@@ -497,7 +497,7 @@
"\r!dlroW olleH", 4, acl_handle, lcid));
}
-TEST_F(StackRfcommTest, MultiServerPortSameDeviceHelloWorld) {
+TEST_F(StackRfcommTest, DISABLED_MultiServerPortSameDeviceHelloWorld) {
// Prepare a server channel at kTestChannelNumber0
static const uint16_t acl_handle = 0x0009;
static const uint16_t lcid = 0x0054;
@@ -546,7 +546,7 @@
acl_handle, lcid));
}
-TEST_F(StackRfcommTest, SameServerPortMultiDeviceHelloWorld) {
+TEST_F(StackRfcommTest, DISABLED_SameServerPortMultiDeviceHelloWorld) {
// Prepare a server channel at kTestChannelNumber0
static const uint16_t test_mtu = 1600;
static const uint8_t test_scn = 3;
@@ -596,7 +596,7 @@
acl_handle_1, lcid_1));
}
-TEST_F(StackRfcommTest, SingleClientConnectionHelloWorld) {
+TEST_F(StackRfcommTest, DISABLED_SingleClientConnectionHelloWorld) {
static const uint16_t acl_handle = 0x0009;
static const uint16_t lcid = 0x0054;
static const uint16_t test_uuid = 0x1112;
@@ -619,7 +619,7 @@
lcid, 0));
}
-TEST_F(StackRfcommTest, MultiClientPortSameDeviceHelloWorld) {
+TEST_F(StackRfcommTest, DISABLED_MultiClientPortSameDeviceHelloWorld) {
static const uint16_t acl_handle = 0x0009;
static const uint16_t lcid = 0x0054;
static const uint16_t test_mtu = 1600;
@@ -665,7 +665,7 @@
acl_handle, lcid, 1));
}
-TEST_F(StackRfcommTest, SameClientPortMultiDeviceHelloWorld) {
+TEST_F(StackRfcommTest, DISABLED_SameClientPortMultiDeviceHelloWorld) {
static const uint16_t test_uuid = 0x1112;
static const uint8_t test_scn = 8;
static const uint16_t test_mtu = 1600;
@@ -713,7 +713,7 @@
acl_handle_1, lcid_1, 1));
}
-TEST_F(StackRfcommTest, TestConnectionCollision) {
+TEST_F(StackRfcommTest, DISABLED_TestConnectionCollision) {
static const uint16_t acl_handle = 0x0008;
static const uint16_t old_lcid = 0x004a;
static const uint16_t new_lcid = 0x005c;
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_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_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__]++; }