Merge "vc: Dont ignore VC devices connected by another gatt client" into tm-qpr-dev
diff --git a/android/app/src/com/android/bluetooth/BluetoothMethodProxy.java b/android/app/src/com/android/bluetooth/BluetoothMethodProxy.java
index d1ea7d4..7fad5b2 100644
--- a/android/app/src/com/android/bluetooth/BluetoothMethodProxy.java
+++ b/android/app/src/com/android/bluetooth/BluetoothMethodProxy.java
@@ -41,6 +41,7 @@
import com.android.bluetooth.gatt.AppAdvertiseStats;
import com.android.bluetooth.gatt.ContextMap;
import com.android.bluetooth.gatt.GattService;
+import com.android.bluetooth.opp.BluetoothOppNotification;
import com.android.internal.annotations.VisibleForTesting;
import com.android.obex.HeaderSet;
@@ -179,6 +180,13 @@
}
/**
+ * Proxies {@link Handler#sendEmptyMessage(int)}}.
+ */
+ public boolean handlerSendEmptyMessage(Handler handler, final int what) {
+ return handler.sendEmptyMessage(what);
+ }
+
+ /**
* Proxies {@link HeaderSet#getHeader}.
*/
public Object getHeader(HeaderSet headerSet, int headerId) throws IOException {
@@ -233,4 +241,13 @@
ContextMap map, GattService service) {
return new AppAdvertiseStats(appUid, id, name, map, service);
}
+
+
+ /**
+ * Proxies {@link com.android.bluetooth.opp.BluetoothOppNotification#BluetoothOppNotification(
+ * Context)}.
+ */
+ public BluetoothOppNotification newBluetoothOppNotification(final Context context) {
+ return new BluetoothOppNotification(context);
+ }
}
diff --git a/android/app/src/com/android/bluetooth/avrcp/AvrcpVolumeManager.java b/android/app/src/com/android/bluetooth/avrcp/AvrcpVolumeManager.java
index d0cfa58..5cf76bd 100644
--- a/android/app/src/com/android/bluetooth/avrcp/AvrcpVolumeManager.java
+++ b/android/app/src/com/android/bluetooth/avrcp/AvrcpVolumeManager.java
@@ -29,6 +29,7 @@
import android.util.Log;
import com.android.bluetooth.audio_util.BTAudioEventLogger;
+import com.android.internal.annotations.VisibleForTesting;
import java.util.HashMap;
import java.util.Map;
@@ -42,7 +43,9 @@
private static final String VOLUME_MAP = "bluetooth_volume_map";
private static final String VOLUME_REJECTLIST = "absolute_volume_rejectlist";
private static final String VOLUME_CHANGE_LOG_TITLE = "Volume Events";
- private static final int AVRCP_MAX_VOL = 127;
+
+ @VisibleForTesting
+ static final int AVRCP_MAX_VOL = 127;
private static final int STREAM_MUSIC = AudioManager.STREAM_MUSIC;
private static final int VOLUME_CHANGE_LOGGER_SIZE = 30;
private static int sDeviceMaxVolume = 0;
diff --git a/android/app/src/com/android/bluetooth/map/BluetoothMapContent.java b/android/app/src/com/android/bluetooth/map/BluetoothMapContent.java
index 19c7911..1c684bf 100644
--- a/android/app/src/com/android/bluetooth/map/BluetoothMapContent.java
+++ b/android/app/src/com/android/bluetooth/map/BluetoothMapContent.java
@@ -40,6 +40,7 @@
import com.android.bluetooth.BluetoothMethodProxy;
import com.android.bluetooth.DeviceWorkArounds;
import com.android.bluetooth.SignedLongLong;
+import com.android.bluetooth.Utils;
import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
import com.android.bluetooth.map.BluetoothMapbMessageMime.MimePart;
import com.android.bluetooth.mapapi.BluetoothMapContract;
@@ -1339,9 +1340,15 @@
}
// Fix Subject Display issue with HONDA Carkit - Ignore subject Mask.
- if (DeviceWorkArounds.addressStartsWith(BluetoothMapService.getRemoteDevice().getAddress(),
- DeviceWorkArounds.HONDA_CARKIT)
- || (ap.getParameterMask() & MASK_SUBJECT) != 0) {
+ boolean isHondaCarkit;
+ if (Utils.isInstrumentationTestMode()) {
+ isHondaCarkit = false;
+ } else {
+ isHondaCarkit = DeviceWorkArounds.addressStartsWith(
+ BluetoothMapService.getRemoteDevice().getAddress(),
+ DeviceWorkArounds.HONDA_CARKIT);
+ }
+ if (isHondaCarkit || (ap.getParameterMask() & MASK_SUBJECT) != 0) {
if (fi.mMsgType == FilterInfo.TYPE_SMS) {
subject = c.getString(fi.mSmsColSubject);
} else if (fi.mMsgType == FilterInfo.TYPE_MMS) {
@@ -2284,7 +2291,8 @@
if (D) {
Log.d(TAG, "msgType: " + fi.mMsgType + " where: " + where);
}
- smsCursor = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION, where, null,
+ smsCursor = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
+ Sms.CONTENT_URI, SMS_PROJECTION, where, null,
Sms.DATE + " DESC" + limit);
if (smsCursor != null) {
BluetoothMapMessageListingElement e = null;
@@ -2326,7 +2334,8 @@
if (D) {
Log.d(TAG, "msgType: " + fi.mMsgType + " where: " + where);
}
- mmsCursor = mResolver.query(Mms.CONTENT_URI, MMS_PROJECTION, where, null,
+ mmsCursor = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
+ Mms.CONTENT_URI, MMS_PROJECTION, where, null,
Mms.DATE + " DESC" + limit);
if (mmsCursor != null) {
BluetoothMapMessageListingElement e = null;
@@ -2369,10 +2378,9 @@
Log.d(TAG, "msgType: " + fi.mMsgType + " where: " + where);
}
Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
- emailCursor =
- mResolver.query(contentUri, BluetoothMapContract.BT_MESSAGE_PROJECTION,
- where, null,
- BluetoothMapContract.MessageColumns.DATE + " DESC" + limit);
+ emailCursor = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
+ contentUri, BluetoothMapContract.BT_MESSAGE_PROJECTION, where, null,
+ BluetoothMapContract.MessageColumns.DATE + " DESC" + limit);
if (emailCursor != null) {
BluetoothMapMessageListingElement e = null;
// store column index so we dont have to look them up anymore (optimization)
@@ -2413,8 +2421,8 @@
}
Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
- imCursor = mResolver.query(contentUri,
- BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION, where, null,
+ imCursor = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
+ contentUri, BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION, where, null,
BluetoothMapContract.MessageColumns.DATE + " DESC" + limit);
if (imCursor != null) {
BluetoothMapMessageListingElement e = null;
@@ -2522,8 +2530,8 @@
if (smsSelected(fi, ap) && folderElement.hasSmsMmsContent()) {
fi.mMsgType = FilterInfo.TYPE_SMS;
String where = setWhereFilter(folderElement, fi, ap);
- Cursor c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION, where, null,
- Sms.DATE + " DESC");
+ Cursor c = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
+ Sms.CONTENT_URI, SMS_PROJECTION, where, null, Sms.DATE + " DESC");
try {
if (c != null) {
cnt = c.getCount();
@@ -2538,8 +2546,8 @@
if (mmsSelected(ap) && folderElement.hasSmsMmsContent()) {
fi.mMsgType = FilterInfo.TYPE_MMS;
String where = setWhereFilter(folderElement, fi, ap);
- Cursor c = mResolver.query(Mms.CONTENT_URI, MMS_PROJECTION, where, null,
- Mms.DATE + " DESC");
+ Cursor c = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
+ Mms.CONTENT_URI, MMS_PROJECTION, where, null, Mms.DATE + " DESC");
try {
if (c != null) {
cnt += c.getCount();
@@ -2556,8 +2564,9 @@
String where = setWhereFilter(folderElement, fi, ap);
if (!where.isEmpty()) {
Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
- Cursor c = mResolver.query(contentUri, BluetoothMapContract.BT_MESSAGE_PROJECTION,
- where, null, BluetoothMapContract.MessageColumns.DATE + " DESC");
+ Cursor c = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
+ contentUri, BluetoothMapContract.BT_MESSAGE_PROJECTION, where, null,
+ BluetoothMapContract.MessageColumns.DATE + " DESC");
try {
if (c != null) {
cnt += c.getCount();
@@ -2575,8 +2584,8 @@
String where = setWhereFilter(folderElement, fi, ap);
if (!where.isEmpty()) {
Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
- Cursor c = mResolver.query(contentUri,
- BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION, where, null,
+ Cursor c = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
+ contentUri, BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION, where, null,
BluetoothMapContract.MessageColumns.DATE + " DESC");
try {
if (c != null) {
@@ -2618,8 +2627,8 @@
String where = setWhereFilterFolderType(folderElement, fi);
where += " AND " + Sms.READ + "=0 ";
where += setWhereFilterPeriod(ap, fi);
- Cursor c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION, where, null,
- Sms.DATE + " DESC");
+ Cursor c = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
+ Sms.CONTENT_URI, SMS_PROJECTION, where, null, Sms.DATE + " DESC");
try {
if (c != null) {
cnt = c.getCount();
@@ -2636,8 +2645,8 @@
String where = setWhereFilterFolderType(folderElement, fi);
where += " AND " + Mms.READ + "=0 ";
where += setWhereFilterPeriod(ap, fi);
- Cursor c = mResolver.query(Mms.CONTENT_URI, MMS_PROJECTION, where, null,
- Sms.DATE + " DESC");
+ Cursor c = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
+ Mms.CONTENT_URI, MMS_PROJECTION, where, null, Sms.DATE + " DESC");
try {
if (c != null) {
cnt += c.getCount();
@@ -2657,8 +2666,9 @@
where += " AND " + BluetoothMapContract.MessageColumns.FLAG_READ + "=0 ";
where += setWhereFilterPeriod(ap, fi);
Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
- Cursor c = mResolver.query(contentUri, BluetoothMapContract.BT_MESSAGE_PROJECTION,
- where, null, BluetoothMapContract.MessageColumns.DATE + " DESC");
+ Cursor c = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
+ contentUri, BluetoothMapContract.BT_MESSAGE_PROJECTION, where, null,
+ BluetoothMapContract.MessageColumns.DATE + " DESC");
try {
if (c != null) {
cnt += c.getCount();
@@ -2678,8 +2688,8 @@
where += " AND " + BluetoothMapContract.MessageColumns.FLAG_READ + "=0 ";
where += setWhereFilterPeriod(ap, fi);
Uri contentUri = Uri.parse(mBaseUri + BluetoothMapContract.TABLE_MESSAGE);
- Cursor c = mResolver.query(contentUri,
- BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION, where, null,
+ Cursor c = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
+ contentUri, BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION, where, null,
BluetoothMapContract.MessageColumns.DATE + " DESC");
try {
if (c != null) {
diff --git a/android/app/src/com/android/bluetooth/map/BluetoothMapContentObserver.java b/android/app/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
index 1862f33..c6adaf7 100644
--- a/android/app/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
+++ b/android/app/src/com/android/bluetooth/map/BluetoothMapContentObserver.java
@@ -1264,7 +1264,8 @@
}
}
- private void initMsgList() throws RemoteException {
+ @VisibleForTesting
+ void initMsgList() throws RemoteException {
if (V) {
Log.d(TAG, "initMsgList");
}
@@ -1278,7 +1279,8 @@
Cursor c;
try {
- c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION_SHORT, null, null, null);
+ c = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
+ Sms.CONTENT_URI, SMS_PROJECTION_SHORT, null, null, null);
} catch (SQLiteException e) {
Log.e(TAG, "Failed to initialize the list of messages: " + e.toString());
return;
@@ -1309,7 +1311,8 @@
HashMap<Long, Msg> msgListMms = new HashMap<Long, Msg>();
- c = mResolver.query(Mms.CONTENT_URI, MMS_PROJECTION_SHORT, null, null, null);
+ c = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver, Mms.CONTENT_URI,
+ MMS_PROJECTION_SHORT, null, null, null);
try {
if (c != null && c.moveToFirst()) {
do {
@@ -1419,7 +1422,8 @@
}
}
- private void handleMsgListChangesSms() {
+ @VisibleForTesting
+ void handleMsgListChangesSms() {
if (V) {
Log.d(TAG, "handleMsgListChangesSms");
}
@@ -1430,9 +1434,11 @@
Cursor c;
synchronized (getMsgListSms()) {
if (mMapEventReportVersion == BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
- c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION_SHORT, null, null, null);
+ c = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
+ Sms.CONTENT_URI, SMS_PROJECTION_SHORT, null, null, null);
} else {
- c = mResolver.query(Sms.CONTENT_URI, SMS_PROJECTION_SHORT_EXT, null, null, null);
+ c = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
+ Sms.CONTENT_URI, SMS_PROJECTION_SHORT_EXT, null, null, null);
}
try {
if (c != null && c.moveToFirst()) {
@@ -1461,14 +1467,8 @@
if (mTransmitEvents && // extract contact details only if needed
mMapEventReportVersion
> BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
- 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 date = BluetoothMapUtils.getDateTimeString(
+ c.getLong(c.getColumnIndex(Sms.DATE)));
String subject = c.getString(c.getColumnIndex(Sms.BODY));
if (subject == null) {
subject = "";
@@ -1639,14 +1639,8 @@
if (mTransmitEvents && // extract contact details only if needed
mMapEventReportVersion
!= BluetoothMapUtils.MAP_EVENT_REPORT_V10) {
- 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 date = BluetoothMapUtils.getDateTimeString(
+ c.getLong(c.getColumnIndex(Mms.DATE)));
String subject = c.getString(c.getColumnIndex(Mms.SUBJECT));
if (subject == null || subject.length() == 0) {
/* Get subject from mms text body parts - if any exists */
diff --git a/android/app/src/com/android/bluetooth/map/BluetoothMapObexServer.java b/android/app/src/com/android/bluetooth/map/BluetoothMapObexServer.java
index f855c78..c02fe4a 100644
--- a/android/app/src/com/android/bluetooth/map/BluetoothMapObexServer.java
+++ b/android/app/src/com/android/bluetooth/map/BluetoothMapObexServer.java
@@ -1308,7 +1308,8 @@
* @param overwrite True: The msgType will be overwritten to match the message types supported
* by this MAS instance. False: any unsupported message types will be masked out.
*/
- private void setMsgTypeFilterParams(BluetoothMapAppParams appParams, boolean overwrite) {
+ @VisibleForTesting
+ void setMsgTypeFilterParams(BluetoothMapAppParams appParams, boolean overwrite) {
int masFilterMask = 0;
if (!mEnableSmsMms) {
masFilterMask |= BluetoothMapAppParams.FILTER_NO_SMS_CDMA;
diff --git a/android/app/src/com/android/bluetooth/map/BluetoothMapUtils.java b/android/app/src/com/android/bluetooth/map/BluetoothMapUtils.java
index 7d7047f..a3710a3 100644
--- a/android/app/src/com/android/bluetooth/map/BluetoothMapUtils.java
+++ b/android/app/src/com/android/bluetooth/map/BluetoothMapUtils.java
@@ -677,21 +677,6 @@
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/opp/BluetoothOppLauncherActivity.java b/android/app/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java
index 301b39f..b5dd0f7 100644
--- a/android/app/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java
+++ b/android/app/src/com/android/bluetooth/opp/BluetoothOppLauncherActivity.java
@@ -422,7 +422,8 @@
launchDevicePicker();
finish();
} catch (IllegalArgumentException exception) {
- showToast(exception.getMessage());
+ String message = exception.getMessage();
+ showToast(message != null ? message : "IllegalArgumentException");
finish();
}
}
diff --git a/android/app/src/com/android/bluetooth/opp/BluetoothOppNotification.java b/android/app/src/com/android/bluetooth/opp/BluetoothOppNotification.java
index af138f2..5194c89 100644
--- a/android/app/src/com/android/bluetooth/opp/BluetoothOppNotification.java
+++ b/android/app/src/com/android/bluetooth/opp/BluetoothOppNotification.java
@@ -61,7 +61,7 @@
* where there is an ongoing transfer, incoming transfer need confirm and
* complete (successful or failed) transfer.
*/
-class BluetoothOppNotification {
+public class BluetoothOppNotification {
private static final String TAG = "BluetoothOppNotification";
private static final boolean V = Constants.VERBOSE;
@@ -152,7 +152,7 @@
* @param ctx The context to use to obtain access to the Notification
* Service
*/
- BluetoothOppNotification(Context ctx) {
+ public BluetoothOppNotification(Context ctx) {
mContext = ctx;
mNotificationMgr = mContext.getSystemService(NotificationManager.class);
mNotificationChannel = new NotificationChannel(OPP_NOTIFICATION_CHANNEL,
diff --git a/android/app/src/com/android/bluetooth/opp/BluetoothOppService.java b/android/app/src/com/android/bluetooth/opp/BluetoothOppService.java
index 773d771..6f20106 100644
--- a/android/app/src/com/android/bluetooth/opp/BluetoothOppService.java
+++ b/android/app/src/com/android/bluetooth/opp/BluetoothOppService.java
@@ -55,6 +55,7 @@
import android.sysprop.BluetoothProperties;
import android.util.Log;
+import com.android.bluetooth.BluetoothMethodProxy;
import com.android.bluetooth.BluetoothObexTransport;
import com.android.bluetooth.IObexConnectionHandler;
import com.android.bluetooth.ObexServerSockets;
@@ -128,7 +129,8 @@
private BluetoothShareContentObserver mObserver;
/** Class to handle Notification Manager updates */
- private BluetoothOppNotification mNotifier;
+ @VisibleForTesting
+ BluetoothOppNotification mNotifier;
private boolean mPendingUpdate;
@@ -136,9 +138,11 @@
private boolean mUpdateThreadRunning;
- private ArrayList<BluetoothOppShareInfo> mShares;
+ @VisibleForTesting
+ ArrayList<BluetoothOppShareInfo> mShares;
- private ArrayList<BluetoothOppBatch> mBatches;
+ @VisibleForTesting
+ ArrayList<BluetoothOppBatch> mBatches;
private BluetoothOppTransfer mTransfer;
@@ -182,7 +186,8 @@
+ BluetoothShare.USER_CONFIRMATION + "="
+ BluetoothShare.USER_CONFIRMATION_PENDING;
- private static final String WHERE_INVISIBLE_UNCONFIRMED =
+ @VisibleForTesting
+ static final String WHERE_INVISIBLE_UNCONFIRMED =
"(" + BluetoothShare.STATUS + " > " + BluetoothShare.STATUS_SUCCESS + " AND "
+ INVISIBLE + ") OR (" + WHERE_CONFIRM_PENDING_INBOUND + ")";
@@ -261,7 +266,7 @@
mAdapterService = AdapterService.getAdapterService();
mObserver = new BluetoothShareContentObserver();
getContentResolver().registerContentObserver(BluetoothShare.CONTENT_URI, true, mObserver);
- mNotifier = new BluetoothOppNotification(this);
+ mNotifier = BluetoothMethodProxy.getInstance().newBluetoothOppNotification(this);
mNotifier.mNotificationMgr.cancelAll();
mNotifier.updateNotification();
updateFromProvider();
@@ -990,7 +995,8 @@
/**
* Removes the local copy of the info about a share.
*/
- private void deleteShare(int arrayPos) {
+ @VisibleForTesting
+ void deleteShare(int arrayPos) {
BluetoothOppShareInfo info = mShares.get(arrayPos);
/*
@@ -1114,18 +1120,25 @@
}
// Run in a background thread at boot.
- private static void trimDatabase(ContentResolver contentResolver) {
+ @VisibleForTesting
+ static void trimDatabase(ContentResolver contentResolver) {
+ if (contentResolver.acquireContentProviderClient(BluetoothShare.CONTENT_URI) == null) {
+ Log.w(TAG, "ContentProvider doesn't exist");
+ return;
+ }
+
// remove the invisible/unconfirmed inbound shares
- int delNum = contentResolver.delete(BluetoothShare.CONTENT_URI, WHERE_INVISIBLE_UNCONFIRMED,
- null);
+ int delNum = BluetoothMethodProxy.getInstance().contentResolverDelete(
+ contentResolver, BluetoothShare.CONTENT_URI, WHERE_INVISIBLE_UNCONFIRMED, null);
+
if (V) {
Log.v(TAG, "Deleted shares, number = " + delNum);
}
// Keep the latest inbound and successful shares.
- Cursor cursor =
- contentResolver.query(BluetoothShare.CONTENT_URI, new String[]{BluetoothShare._ID},
- WHERE_INBOUND_SUCCESS, null, BluetoothShare._ID); // sort by id
+ Cursor cursor = BluetoothMethodProxy.getInstance().contentResolverQuery(
+ contentResolver, BluetoothShare.CONTENT_URI, new String[]{BluetoothShare._ID},
+ WHERE_INBOUND_SUCCESS, null, BluetoothShare._ID); // sort by id
if (cursor == null) {
return;
}
@@ -1136,8 +1149,8 @@
if (cursor.moveToPosition(numToDelete)) {
int columnId = cursor.getColumnIndexOrThrow(BluetoothShare._ID);
long id = cursor.getLong(columnId);
- delNum = contentResolver.delete(BluetoothShare.CONTENT_URI,
- BluetoothShare._ID + " < " + id, null);
+ delNum = BluetoothMethodProxy.getInstance().contentResolverDelete(contentResolver,
+ BluetoothShare.CONTENT_URI, BluetoothShare._ID + " < " + id, null);
if (V) {
Log.v(TAG, "Deleted old inbound success share: " + delNum);
}
diff --git a/android/app/src/com/android/bluetooth/opp/BluetoothOppTransfer.java b/android/app/src/com/android/bluetooth/opp/BluetoothOppTransfer.java
index 00fb596..eb10b23 100644
--- a/android/app/src/com/android/bluetooth/opp/BluetoothOppTransfer.java
+++ b/android/app/src/com/android/bluetooth/opp/BluetoothOppTransfer.java
@@ -92,23 +92,27 @@
private BluetoothAdapter mAdapter;
- private BluetoothDevice mDevice;
+ @VisibleForTesting
+ BluetoothDevice mDevice;
private BluetoothOppBatch mBatch;
private BluetoothOppObexSession mSession;
- private BluetoothOppShareInfo mCurrentShare;
+ @VisibleForTesting
+ BluetoothOppShareInfo mCurrentShare;
private ObexTransport mTransport;
private HandlerThread mHandlerThread;
- private EventHandler mSessionHandler;
+ @VisibleForTesting
+ EventHandler mSessionHandler;
private long mTimestamp;
- private class OppConnectionReceiver extends BroadcastReceiver {
+ @VisibleForTesting
+ class OppConnectionReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
@@ -139,8 +143,8 @@
// Remove the timeout message triggered earlier during Obex Put
mSessionHandler.removeMessages(BluetoothOppObexSession.MSG_CONNECT_TIMEOUT);
// Now reuse the same message to clean up the session.
- mSessionHandler.sendMessage(mSessionHandler.obtainMessage(
- BluetoothOppObexSession.MSG_CONNECT_TIMEOUT));
+ BluetoothMethodProxy.getInstance().handlerSendEmptyMessage(mSessionHandler,
+ BluetoothOppObexSession.MSG_CONNECT_TIMEOUT);
}
} catch (Exception e) {
e.printStackTrace();
@@ -160,7 +164,11 @@
Log.w(TAG, "OPP SDP search, target device is null, ignoring result");
return;
}
- if (!device.getIdentityAddress().equalsIgnoreCase(mDevice.getIdentityAddress())) {
+ String deviceIdentityAddress = device.getIdentityAddress();
+ String transferDeviceIdentityAddress = mDevice.getIdentityAddress();
+ if (deviceIdentityAddress == null || transferDeviceIdentityAddress == null
+ || !deviceIdentityAddress.equalsIgnoreCase(
+ transferDeviceIdentityAddress)) {
Log.w(TAG, " OPP SDP search for wrong device, ignoring!!");
return;
}
@@ -676,10 +684,12 @@
@VisibleForTesting
SocketConnectThread mConnectThread;
- private class SocketConnectThread extends Thread {
+ @VisibleForTesting
+ class SocketConnectThread extends Thread {
private final String mHost;
- private final BluetoothDevice mDevice;
+ @VisibleForTesting
+ final BluetoothDevice mDevice;
private final int mChannel;
@@ -695,7 +705,8 @@
private boolean mSdpInitiated = false;
- private boolean mIsInterrupted = false;
+ @VisibleForTesting
+ boolean mIsInterrupted = false;
/* create a Rfcomm/L2CAP Socket */
SocketConnectThread(BluetoothDevice device, boolean retry) {
@@ -863,7 +874,8 @@
Log.e(TAG, "Error when close socket");
}
}
- mSessionHandler.obtainMessage(TRANSPORT_ERROR).sendToTarget();
+ BluetoothMethodProxy.getInstance().handlerSendEmptyMessage(mSessionHandler,
+ TRANSPORT_ERROR);
return;
}
diff --git a/android/app/src/com/android/bluetooth/telephony/BluetoothInCallService.java b/android/app/src/com/android/bluetooth/telephony/BluetoothInCallService.java
index 46eb260..7cc5bac 100644
--- a/android/app/src/com/android/bluetooth/telephony/BluetoothInCallService.java
+++ b/android/app/src/com/android/bluetooth/telephony/BluetoothInCallService.java
@@ -708,7 +708,7 @@
// We do, however want to send conferences that have no children to the bluetooth
// device (e.g. IMS Conference).
boolean isConferenceWithNoChildren = isConferenceWithNoChildren(call);
- Log.i(TAG, "sendListOfCalls isConferenceWithNoChildren " + isConferenceWithNoChildren
+ Log.i(TAG, "sendListOfCalls isConferenceWithNoChildren " + isConferenceWithNoChildren
+ ", call.getChildrenIds() size " + call.getChildrenIds().size());
if (!call.isConference() || isConferenceWithNoChildren) {
sendClccForCall(call, shouldLog);
@@ -1479,7 +1479,9 @@
mBluetoothLeCallControl.currentCallsList(tbsCalls);
}
- private final BluetoothLeCallControl.Callback mBluetoothLeCallControlCallback = new BluetoothLeCallControl.Callback() {
+ @VisibleForTesting
+ final BluetoothLeCallControl.Callback mBluetoothLeCallControlCallback =
+ new BluetoothLeCallControl.Callback() {
@Override
public void onAcceptCall(int requestId, UUID callId) {
diff --git a/android/app/tests/unit/src/com/android/bluetooth/BluetoothPrefsTest.java b/android/app/tests/unit/src/com/android/bluetooth/BluetoothPrefsTest.java
deleted file mode 100644
index dc8ba43..0000000
--- a/android/app/tests/unit/src/com/android/bluetooth/BluetoothPrefsTest.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * 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/audio_util/GPMWrapperTest.java b/android/app/tests/unit/src/com/android/bluetooth/audio_util/GPMWrapperTest.java
index 0ef8a29..7f03b37 100644
--- 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
@@ -111,6 +111,6 @@
GPMWrapper wrapper = new GPMWrapper(mContext, mMediaController, null);
- assertThat(wrapper.isMetadataSynced()).isFalse();
+ assertThat(wrapper.isMetadataSynced()).isTrue();
}
}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/avrcp/AvrcpVolumeManagerTest.java b/android/app/tests/unit/src/com/android/bluetooth/avrcp/AvrcpVolumeManagerTest.java
new file mode 100644
index 0000000..dd57998
--- /dev/null
+++ b/android/app/tests/unit/src/com/android/bluetooth/avrcp/AvrcpVolumeManagerTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.avrcp;
+
+import static com.android.bluetooth.avrcp.AvrcpVolumeManager.AVRCP_MAX_VOL;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.media.AudioManager;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class AvrcpVolumeManagerTest {
+ private static final String REMOTE_DEVICE_ADDRESS = "00:01:02:03:04:05";
+ private static final int TEST_DEVICE_MAX_VOUME = 25;
+
+ @Mock
+ AvrcpNativeInterface mNativeInterface;
+
+ @Mock
+ AudioManager mAudioManager;
+
+ Context mContext;
+ BluetoothDevice mRemoteDevice;
+ AvrcpVolumeManager mAvrcpVolumeManager;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mContext = InstrumentationRegistry.getTargetContext();
+ when(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
+ .thenReturn(TEST_DEVICE_MAX_VOUME);
+ mRemoteDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(REMOTE_DEVICE_ADDRESS);
+ mAvrcpVolumeManager = new AvrcpVolumeManager(mContext, mAudioManager, mNativeInterface);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mAvrcpVolumeManager.removeStoredVolumeForDevice(mRemoteDevice);
+ }
+
+ @Test
+ public void avrcpToSystemVolume() {
+ assertThat(AvrcpVolumeManager.avrcpToSystemVolume(0)).isEqualTo(0);
+ assertThat(AvrcpVolumeManager.avrcpToSystemVolume(AVRCP_MAX_VOL))
+ .isEqualTo(TEST_DEVICE_MAX_VOUME);
+ }
+
+ @Test
+ public void dump() {
+ StringBuilder sb = new StringBuilder();
+ mAvrcpVolumeManager.dump(sb);
+
+ assertThat(sb.toString()).isNotEmpty();
+ }
+
+ @Test
+ public void sendVolumeChanged() {
+ mAvrcpVolumeManager.sendVolumeChanged(mRemoteDevice, TEST_DEVICE_MAX_VOUME);
+
+ verify(mNativeInterface).sendVolumeChanged(REMOTE_DEVICE_ADDRESS, AVRCP_MAX_VOL);
+ }
+
+ @Test
+ public void setVolume() {
+ mAvrcpVolumeManager.setVolume(mRemoteDevice, AVRCP_MAX_VOL);
+
+ verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC),
+ eq(TEST_DEVICE_MAX_VOUME), anyInt());
+ }
+}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/bas/BatteryServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/bas/BatteryServiceTest.java
index 8c5ee71..e4a706d 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/bas/BatteryServiceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/bas/BatteryServiceTest.java
@@ -242,6 +242,31 @@
Assert.assertFalse("Connect expected to fail", mService.connect(mDevice));
}
+ @Test
+ public void getConnectionState_whenNoDevicesAreConnected_returnsDisconnectedState() {
+ Assert.assertEquals(mService.getConnectionState(mDevice),
+ BluetoothProfile.STATE_DISCONNECTED);
+ }
+
+ @Test
+ public void getDevices_whenNoDevicesAreConnected_returnsEmptyList() {
+ Assert.assertTrue(mService.getDevices().isEmpty());
+ }
+
+ @Test
+ public void getDevicesMatchingConnectionStates() {
+ when(mAdapterService.getBondedDevices()).thenReturn(new BluetoothDevice[] {mDevice});
+ int states[] = new int[] {BluetoothProfile.STATE_DISCONNECTED};
+
+ Assert.assertTrue(mService.getDevicesMatchingConnectionStates(states).contains(mDevice));
+ }
+
+ @Test
+ public void setConnectionPolicy() {
+ Assert.assertTrue(mService.setConnectionPolicy(
+ mDevice, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN));
+ }
+
/**
* Helper function to test okToConnect() method
*
diff --git a/android/app/tests/unit/src/com/android/bluetooth/bass_client/PeriodicAdvertisementResultTest.java b/android/app/tests/unit/src/com/android/bluetooth/bass_client/PeriodicAdvertisementResultTest.java
new file mode 100644
index 0000000..b80c949
--- /dev/null
+++ b/android/app/tests/unit/src/com/android/bluetooth/bass_client/PeriodicAdvertisementResultTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.bass_client;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class PeriodicAdvertisementResultTest {
+ private static final String REMOTE_DEVICE_ADDRESS = "00:01:02:03:04:05";
+
+ BluetoothDevice mDevice;
+
+ @Before
+ public void setUp() {
+ mDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(REMOTE_DEVICE_ADDRESS);
+ }
+
+ @Test
+ public void constructor() {
+ int addressType = 1;
+ int syncHandle = 2;
+ int advSid = 3;
+ int paInterval = 4;
+ int broadcastId = 5;
+ PeriodicAdvertisementResult result = new PeriodicAdvertisementResult(
+ mDevice, addressType, syncHandle, advSid, paInterval, broadcastId);
+
+ assertThat(result.getAddressType()).isEqualTo(addressType);
+ assertThat(result.getSyncHandle()).isEqualTo(syncHandle);
+ assertThat(result.getAdvSid()).isEqualTo(advSid);
+ assertThat(result.getAdvInterval()).isEqualTo(paInterval);
+ assertThat(result.getBroadcastId()).isEqualTo(broadcastId);
+ }
+
+ @Test
+ public void updateMethods() {
+ int addressType = 1;
+ int syncHandle = 2;
+ int advSid = 3;
+ int paInterval = 4;
+ int broadcastId = 5;
+ PeriodicAdvertisementResult result = new PeriodicAdvertisementResult(
+ mDevice, addressType, syncHandle, advSid, paInterval, broadcastId);
+
+ int newAddressType = 6;
+ result.updateAddressType(newAddressType);
+ assertThat(result.getAddressType()).isEqualTo(newAddressType);
+
+ int newSyncHandle = 7;
+ result.updateSyncHandle(newSyncHandle);
+ assertThat(result.getSyncHandle()).isEqualTo(newSyncHandle);
+
+ int newAdvSid = 8;
+ result.updateAdvSid(newAdvSid);
+ assertThat(result.getAdvSid()).isEqualTo(newAdvSid);
+
+ int newAdvInterval = 9;
+ result.updateAdvInterval(newAdvInterval);
+ assertThat(result.getAdvInterval()).isEqualTo(newAdvInterval);
+
+ int newBroadcastId = 10;
+ result.updateBroadcastId(newBroadcastId);
+ assertThat(result.getBroadcastId()).isEqualTo(newBroadcastId);
+ }
+
+ @Test
+ public void print_doesNotCrash() {
+ int addressType = 1;
+ int syncHandle = 2;
+ int advSid = 3;
+ int paInterval = 4;
+ int broadcastId = 5;
+ PeriodicAdvertisementResult result = new PeriodicAdvertisementResult(
+ mDevice, addressType, syncHandle, advSid, paInterval, broadcastId);
+
+ result.print();
+ }
+}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/btservice/AdapterServiceTest.java
index 29414ca..4d30966 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
@@ -738,6 +738,7 @@
/**
* Test: Check if obfuscated Bluetooth address stays the same after toggling Bluetooth
*/
+ @Ignore("b/265588558")
@Test
public void testObfuscateBluetoothAddress_PersistentBetweenToggle() {
Assert.assertFalse(mAdapterService.getState() == BluetoothAdapter.STATE_ON);
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 a73d24c..141ee85 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
@@ -58,6 +58,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;
@@ -88,7 +89,6 @@
@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;
@@ -119,7 +119,6 @@
mService.mClientMap = mClientMap;
mService.mScannerMap = mScannerMap;
- mService.mPeriodicScanManager = mPeriodicScanManager;
mService.mScanManager = mScanManager;
mService.mReliableQueue = mReliableQueue;
mService.mServerMap = mServerMap;
@@ -626,6 +625,7 @@
mAttributionSource);
}
+ @Ignore("b/265327402")
@Test
public void registerSync() {
ScanResult scanResult = new ScanResult(mDevice, 1, 2, 3, 4, 5, 6, 7, null, 8);
@@ -634,7 +634,6 @@
IPeriodicAdvertisingCallback callback = mock(IPeriodicAdvertisingCallback.class);
mService.registerSync(scanResult, skip, timeout, callback, mAttributionSource);
- verify(mPeriodicScanManager).startSync(scanResult, skip, timeout, callback);
}
@Test
@@ -643,9 +642,9 @@
int syncHandle = 2;
mService.transferSync(mDevice, serviceData, syncHandle, mAttributionSource);
- verify(mPeriodicScanManager).transferSync(mDevice, serviceData, syncHandle);
}
+ @Ignore("b/265327402")
@Test
public void transferSetInfo() {
int serviceData = 1;
@@ -654,15 +653,14 @@
mService.transferSetInfo(mDevice, serviceData, advHandle, callback,
mAttributionSource);
- verify(mPeriodicScanManager).transferSetInfo(mDevice, serviceData, advHandle, callback);
}
+ @Ignore("b/265327402")
@Test
public void unregisterSync() {
IPeriodicAdvertisingCallback callback = mock(IPeriodicAdvertisingCallback.class);
mService.unregisterSync(callback, mAttributionSource);
- verify(mPeriodicScanManager).stopSync(callback);
}
@Test
diff --git a/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetStateMachineTest.java b/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetStateMachineTest.java
index 971e53b..a3e58d9 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetStateMachineTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetStateMachineTest.java
@@ -57,6 +57,7 @@
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -1186,6 +1187,7 @@
Assert.assertEquals(mHeadsetStateMachine.parseUnknownAt(atString), "A\"command\"");
}
+ @Ignore("b/265556073")
@Test
public void testHandleAccessPermissionResult_withNoChangeInAtCommandResult() {
when(mIntent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)).thenReturn(null);
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 5661492..8068857 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
@@ -31,6 +31,7 @@
import android.os.Looper;
import android.os.RemoteException;
import android.os.UserManager;
+import android.provider.ContactsContract;
import android.provider.Telephony;
import android.provider.Telephony.Mms;
import android.provider.Telephony.Sms;
@@ -45,6 +46,7 @@
import com.android.bluetooth.SignedLongLong;
import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
import com.android.bluetooth.mapapi.BluetoothMapContract;
+import com.android.bluetooth.mapapi.BluetoothMapContract.MessageColumns;
import com.android.obex.ResponseCodes;
import com.google.android.mms.pdu.PduHeaders;
@@ -92,6 +94,8 @@
static final int TEST_MMS_MTYPE = 1;
static final int TEST_MMS_TYPE_ALL = Telephony.BaseMmsColumns.MESSAGE_BOX_ALL;
static final int TEST_MMS_TYPE_INBOX = Telephony.BaseMmsColumns.MESSAGE_BOX_INBOX;
+ static final int TEST_SMS_TYPE_ALL = Telephony.TextBasedSmsColumns.MESSAGE_TYPE_ALL;
+ static final int TEST_SMS_TYPE_INBOX = Telephony.BaseMmsColumns.MESSAGE_BOX_INBOX;
static final Uri TEST_URI = Mms.CONTENT_URI;
static final String TEST_AUTHORITY = "test_authority";
@@ -822,9 +826,75 @@
}
@Test
+ public void initMsgList_withMsgSms() throws Exception {
+ MatrixCursor cursor = new MatrixCursor(new String[] {Sms._ID, Sms.TYPE, Sms.THREAD_ID,
+ Sms.READ});
+ cursor.addRow(new Object[] {(long) TEST_ID, TEST_SMS_TYPE_ALL, TEST_THREAD_ID,
+ TEST_READ_FLAG_ONE});
+ doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+ eq(BluetoothMapContentObserver.SMS_PROJECTION_SHORT), any(), any(), any());
+ cursor.moveToFirst();
+ Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
+ mObserver.setMsgListMsg(map, true);
+
+ mObserver.initMsgList();
+
+ BluetoothMapContentObserver.Msg msg = mObserver.getMsgListSms().get((long) TEST_ID);
+ Assert.assertEquals(msg.id, TEST_ID);
+ Assert.assertEquals(msg.type, TEST_SMS_TYPE_ALL);
+ Assert.assertEquals(msg.threadId, TEST_THREAD_ID);
+ Assert.assertEquals(msg.flagRead, TEST_READ_FLAG_ONE);
+ }
+
+ @Test
+ public void initMsgList_withMsgMms() throws Exception {
+ MatrixCursor cursor = new MatrixCursor(new String[] {Mms._ID, Mms.MESSAGE_BOX,
+ Mms.THREAD_ID, Mms.READ});
+ cursor.addRow(new Object[] {(long) TEST_ID, TEST_MMS_TYPE_ALL, TEST_THREAD_ID,
+ TEST_READ_FLAG_ZERO});
+ doReturn(null).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+ eq(BluetoothMapContentObserver.SMS_PROJECTION_SHORT), any(), any(), any());
+ doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+ eq(BluetoothMapContentObserver.MMS_PROJECTION_SHORT), any(), any(), any());
+ cursor.moveToFirst();
+ Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
+ mObserver.setMsgListMsg(map, true);
+
+ mObserver.initMsgList();
+
+ BluetoothMapContentObserver.Msg msg = mObserver.getMsgListMms().get((long) TEST_ID);
+ Assert.assertEquals(msg.id, TEST_ID);
+ Assert.assertEquals(msg.type, TEST_MMS_TYPE_ALL);
+ Assert.assertEquals(msg.threadId, TEST_THREAD_ID);
+ Assert.assertEquals(msg.flagRead, TEST_READ_FLAG_ZERO);
+ }
+
+ @Test
+ public void initMsgList_withMsg() throws Exception {
+ MatrixCursor cursor = new MatrixCursor(new String[] {MessageColumns._ID,
+ MessageColumns.FOLDER_ID, MessageColumns.FLAG_READ});
+ cursor.addRow(new Object[] {(long) TEST_ID, TEST_INBOX_FOLDER_ID, TEST_READ_FLAG_ONE});
+ doReturn(null).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+ eq(BluetoothMapContentObserver.SMS_PROJECTION_SHORT), any(), any(), any());
+ doReturn(null).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+ eq(BluetoothMapContentObserver.MMS_PROJECTION_SHORT), any(), any(), any());
+ when(mProviderClient.query(any(), any(), any(), any(), any())).thenReturn(cursor);
+ cursor.moveToFirst();
+ Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
+ mObserver.setMsgListMsg(map, true);
+
+ mObserver.initMsgList();
+
+ BluetoothMapContentObserver.Msg msg = mObserver.getMsgListMsg().get((long) TEST_ID);
+ Assert.assertEquals(msg.id, TEST_ID);
+ Assert.assertEquals(msg.folderId, TEST_INBOX_FOLDER_ID);
+ Assert.assertEquals(msg.flagRead, TEST_READ_FLAG_ONE);
+ }
+
+ @Test
public void initContactsList() throws Exception {
MatrixCursor cursor = new MatrixCursor(
- new String[]{BluetoothMapContract.ConvoContactColumns.CONVO_ID,
+ new String[] {BluetoothMapContract.ConvoContactColumns.CONVO_ID,
BluetoothMapContract.ConvoContactColumns.NAME,
BluetoothMapContract.ConvoContactColumns.NICKNAME,
BluetoothMapContract.ConvoContactColumns.X_BT_UID,
@@ -1102,7 +1172,7 @@
public void handleMsgListChangesMms_withNonExistingMessage_andVersion11() {
MatrixCursor cursor = new MatrixCursor(new String[] {Mms._ID, Mms.MESSAGE_BOX,
Mms.MESSAGE_TYPE, Mms.THREAD_ID, Mms.READ, Mms.DATE, Mms.SUBJECT,
- Mms.PRIORITY, Mms.Addr.ADDRESS});
+ Mms.PRIORITY, Mms.Addr.ADDRESS});
cursor.addRow(new Object[] {TEST_HANDLE_ONE, TEST_MMS_TYPE_ALL, TEST_MMS_MTYPE,
TEST_THREAD_ID, TEST_READ_FLAG_ONE, TEST_DATE, TEST_SUBJECT,
PduHeaders.PRIORITY_HIGH, null});
@@ -1304,6 +1374,179 @@
}
@Test
+ public void handleMsgListChangesSms_withNonExistingMessage_andVersion11() {
+ MatrixCursor cursor = new MatrixCursor(new String[] {Sms._ID, Sms.TYPE, Sms.THREAD_ID,
+ Sms.READ, Sms.DATE, Sms.BODY, Sms.ADDRESS, ContactsContract.Contacts.DISPLAY_NAME});
+ cursor.addRow(new Object[] {TEST_HANDLE_ONE, TEST_SMS_TYPE_INBOX, TEST_THREAD_ID,
+ TEST_READ_FLAG_ONE, TEST_DATE, TEST_SUBJECT, TEST_ADDRESS, null});
+ doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(), any(), any(),
+ any(), any());
+
+ Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
+ // Giving a different handle for msg below and cursor above makes handleMsgListChangesSms()
+ // function for a non-existing message
+ BluetoothMapContentObserver.Msg msg = new BluetoothMapContentObserver.Msg(TEST_HANDLE_TWO,
+ TEST_SMS_TYPE_ALL, TEST_READ_FLAG_ONE);
+ map.put(TEST_HANDLE_TWO, msg);
+ mObserver.setMsgListSms(map, true);
+ mObserver.mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V11;
+
+ mObserver.handleMsgListChangesSms();
+
+ Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).id, TEST_HANDLE_ONE);
+ Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).type,
+ TEST_SMS_TYPE_INBOX);
+ Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).threadId,
+ TEST_THREAD_ID);
+ Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).flagRead,
+ TEST_READ_FLAG_ONE);
+ }
+
+ @Test
+ public void handleMsgListChangesSms_withNonExistingMessage_andVersion12() {
+ MatrixCursor cursor = new MatrixCursor(new String[] {Sms._ID, Sms.TYPE, Sms.THREAD_ID,
+ Sms.READ, Sms.DATE, Sms.BODY, Sms.ADDRESS});
+ cursor.addRow(new Object[] {TEST_HANDLE_ONE, TEST_SMS_TYPE_ALL, TEST_THREAD_ID,
+ TEST_READ_FLAG_ONE, TEST_DATE, "", null});
+ doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(), any(), any(),
+ any(), any());
+
+ Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
+ // Giving a different handle for msg below and cursor above makes handleMsgListChangesSms()
+ // function for a non-existing message
+ BluetoothMapContentObserver.Msg msg = new BluetoothMapContentObserver.Msg(TEST_HANDLE_TWO,
+ TEST_SMS_TYPE_INBOX, TEST_READ_FLAG_ONE);
+ map.put(TEST_HANDLE_TWO, msg);
+ mObserver.setMsgListSms(map, true);
+ mObserver.mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V12;
+
+ mObserver.handleMsgListChangesSms();
+
+ Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).id, TEST_HANDLE_ONE);
+ Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).type,
+ TEST_SMS_TYPE_ALL);
+ Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).threadId,
+ TEST_THREAD_ID);
+ Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).flagRead,
+ TEST_READ_FLAG_ONE);
+ }
+
+ @Test
+ public void handleMsgListChangesSms_withNonExistingMessage_andVersion10() {
+ MatrixCursor cursor = new MatrixCursor(new String[] {Sms._ID, Sms.TYPE, Sms.THREAD_ID,
+ Sms.READ});
+ cursor.addRow(new Object[] {TEST_HANDLE_ONE, TEST_SMS_TYPE_ALL, TEST_THREAD_ID,
+ TEST_READ_FLAG_ONE});
+ doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(), any(), any(),
+ any(), any());
+
+ Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
+ // Giving a different handle for msg below and cursor above makes handleMsgListChangesSms()
+ // function for a non-existing message
+ BluetoothMapContentObserver.Msg msg = new BluetoothMapContentObserver.Msg(TEST_HANDLE_TWO,
+ TEST_SMS_TYPE_INBOX, TEST_READ_FLAG_ONE);
+ map.put(TEST_HANDLE_TWO, msg);
+ mObserver.setMsgListSms(map, true);
+ mObserver.mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V10;
+
+ mObserver.handleMsgListChangesSms();
+
+ Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).id, TEST_HANDLE_ONE);
+ Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).type,
+ TEST_SMS_TYPE_ALL);
+ Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).threadId,
+ TEST_THREAD_ID);
+ Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).flagRead,
+ TEST_READ_FLAG_ONE);
+ }
+
+ @Test
+ public void handleMsgListChangesSms_withExistingMessage_withNonEqualType() {
+ MatrixCursor cursor = new MatrixCursor(new String[] {Sms._ID, Sms.TYPE, Sms.THREAD_ID,
+ Sms.READ});
+ cursor.addRow(new Object[] {TEST_HANDLE_ONE, TEST_SMS_TYPE_ALL, TEST_THREAD_ID,
+ TEST_READ_FLAG_ONE});
+ doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(), any(), any(),
+ any(), any());
+
+ Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
+ // Giving the same handle for msg below and cursor above makes handleMsgListChangesSms()
+ // function for an existing message
+ BluetoothMapContentObserver.Msg msg = new BluetoothMapContentObserver.Msg(TEST_HANDLE_ONE,
+ TEST_SMS_TYPE_INBOX, TEST_THREAD_ID, TEST_READ_FLAG_ZERO);
+ map.put(TEST_HANDLE_ONE, msg);
+ mObserver.setMsgListSms(map, true);
+ mObserver.mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V12;
+
+ mObserver.handleMsgListChangesSms();
+
+ Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).id, TEST_HANDLE_ONE);
+ Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).type,
+ TEST_SMS_TYPE_ALL);
+ Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).threadId,
+ TEST_THREAD_ID);
+ Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).flagRead,
+ TEST_READ_FLAG_ONE);
+ }
+
+ @Test
+ public void handleMsgListChangesSms_withExistingMessage_withDeletedThreadId() {
+ MatrixCursor cursor = new MatrixCursor(new String[] {Sms._ID, Sms.TYPE, Sms.THREAD_ID,
+ Sms.READ});
+ cursor.addRow(new Object[] {TEST_HANDLE_ONE, TEST_SMS_TYPE_ALL,
+ BluetoothMapContentObserver.DELETED_THREAD_ID, TEST_READ_FLAG_ONE});
+ doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(), any(), any(),
+ any(), any());
+
+ Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
+ // Giving the same handle for msg below and cursor above makes handleMsgListChangesSms()
+ // function for an existing message
+ BluetoothMapContentObserver.Msg msg = new BluetoothMapContentObserver.Msg(TEST_HANDLE_ONE,
+ TEST_SMS_TYPE_ALL, TEST_THREAD_ID, TEST_READ_FLAG_ZERO);
+ map.put(TEST_HANDLE_ONE, msg);
+ mObserver.setMsgListSms(map, true);
+ mObserver.mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V12;
+
+ mObserver.handleMsgListChangesSms();
+
+ Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).id, TEST_HANDLE_ONE);
+ Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).type, TEST_SMS_TYPE_ALL);
+ Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).threadId,
+ BluetoothMapContentObserver.DELETED_THREAD_ID);
+ Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).flagRead,
+ TEST_READ_FLAG_ONE);
+ }
+
+ @Test
+ public void handleMsgListChangesSms_withExistingMessage_withUndeletedThreadId() {
+ int undeletedThreadId = 0;
+ MatrixCursor cursor = new MatrixCursor(new String[] {Sms._ID, Sms.TYPE, Sms.THREAD_ID,
+ Sms.READ});
+ cursor.addRow(new Object[] {TEST_HANDLE_ONE, TEST_SMS_TYPE_ALL, undeletedThreadId,
+ TEST_READ_FLAG_ONE});
+ doReturn(cursor).when(mMapMethodProxy).contentResolverQuery(any(), any(), any(), any(),
+ any(), any());
+
+ Map<Long, BluetoothMapContentObserver.Msg> map = new HashMap<>();
+ // Giving the same handle for msg below and cursor above makes handleMsgListChangesSms()
+ // function for an existing message
+ BluetoothMapContentObserver.Msg msg = new BluetoothMapContentObserver.Msg(TEST_HANDLE_ONE,
+ TEST_SMS_TYPE_ALL, TEST_THREAD_ID, TEST_READ_FLAG_ZERO);
+ map.put(TEST_HANDLE_ONE, msg);
+ mObserver.setMsgListSms(map, true);
+ mObserver.mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V12;
+
+ mObserver.handleMsgListChangesSms();
+
+ Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).id, TEST_HANDLE_ONE);
+ Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).type, TEST_SMS_TYPE_ALL);
+ Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).threadId,
+ undeletedThreadId);
+ Assert.assertEquals(mObserver.getMsgListSms().get(TEST_HANDLE_ONE).flagRead,
+ TEST_READ_FLAG_ONE);
+ }
+
+ @Test
public void handleMmsSendIntent_withMnsClientNotConnected() {
when(mClient.isConnected()).thenReturn(false);
@@ -1348,6 +1591,41 @@
}
@Test
+ public void actionMessageSentDisconnected_withTypeMms() {
+ Map<Long, BluetoothMapContentObserver.Msg> mmsMsgList = new HashMap<>();
+ BluetoothMapContentObserver.Msg msg = createSimpleMsg();
+ mmsMsgList.put(TEST_HANDLE_ONE, msg);
+ doReturn(1).when(mIntent).getIntExtra(
+ BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_TRANSPARENT, 0);
+ doReturn((long) -1).when(mIntent).getLongExtra(
+ BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_HANDLE, -1);
+ // This mock sets type to MMS
+ doReturn(4).when(mIntent).getIntExtra(
+ BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_MSG_TYPE, TYPE.NONE.ordinal());
+
+ mObserver.actionMessageSentDisconnected(mContext, mIntent, 1);
+
+ Assert.assertTrue(mmsMsgList.containsKey(TEST_HANDLE_ONE));
+ }
+
+ @Test
+ public void actionMessageSentDisconnected_withTypeEmail() {
+ // This sets to null uriString
+ doReturn(null).when(mIntent).getStringExtra(
+ BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_URI);
+ doReturn(1).when(mIntent).getIntExtra(
+ BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_TRANSPARENT, 0);
+ // This mock sets type to Email
+ doReturn(1).when(mIntent).getIntExtra(
+ BluetoothMapContentObserver.EXTRA_MESSAGE_SENT_MSG_TYPE, TYPE.NONE.ordinal());
+ clearInvocations(mContext);
+
+ mObserver.actionMessageSentDisconnected(mContext, mIntent, Activity.RESULT_FIRST_USER);
+
+ verify(mContext, never()).getContentResolver();
+ }
+
+ @Test
public void actionMmsSent_withInvalidHandle() {
Map<Long, BluetoothMapContentObserver.Msg> mmsMsgList = new HashMap<>();
BluetoothMapContentObserver.Msg msg = createSimpleMsg();
@@ -1571,6 +1849,7 @@
map.put(TEST_UCI, contact);
mObserver.setContactList(map, true);
mObserver.mMapEventReportVersion = BluetoothMapUtils.MAP_EVENT_REPORT_V12;
+ when(mTelephonyManager.getLine1Number()).thenReturn("");
mObserver.handleContactListChanges(uri);
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 6824582..9e1f738 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapContentTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapContentTest.java
@@ -48,6 +48,8 @@
import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
import com.android.bluetooth.mapapi.BluetoothMapContract;
+import com.google.android.mms.pdu.PduHeaders;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -70,8 +72,10 @@
private static final String TEST_BCC_ADDRESS = "bccName (bccAddress) <bcc@google.com>";
private static final String TEST_FROM_ADDRESS = "fromName (fromAddress) <from@google.com>";
private static final String TEST_ADDRESS = "111-1111-1111";
- private static final long TEST_DATE_SMS = 1;
+ private static final long TEST_DATE_SMS = 4;
+ private static final long TEST_DATE_MMS = 3;
private static final long TEST_DATE_EMAIL = 2;
+ private static final long TEST_DATE_IM = 1;
private static final String TEST_NAME = "test_name";
private static final String TEST_FORMATTED_NAME = "test_formatted_name";
private static final String TEST_PHONE = "test_phone";
@@ -87,6 +91,21 @@
private static final String TEST_FIRST_BT_UCI_ORIGINATOR = "test_first_bt_uci_originator";
private static final int TEST_NO_FILTER = 0;
private static final String TEST_CONTACT_NAME_FILTER = "test_contact_name_filter";
+ private static final int TEST_SIZE = 1;
+ private static final int TEST_TEXT_ONLY = 1;
+ private static final int TEST_READ_TRUE = 1;
+ private static final int TEST_READ_FALSE = 0;
+ private static final int TEST_PRIORITY_HIGH = 1;
+ private static final int TEST_SENT_YES = 2;
+ private static final int TEST_SENT_NO = 1;
+ private static final int TEST_PROTECTED = 1;
+ private static final int TEST_ATTACHMENT_TRUE = 1;
+ private static final String TEST_DELIVERY_STATE = "delivered";
+ private static final long TEST_THREAD_ID = 1;
+ private static final String TEST_ATTACHMENT_MIME_TYPE = "test_mime_type";
+ private static final String TEST_YES = "yes";
+ private static final String TEST_NO = "no";
+ private static final String TEST_RECEPTION_STATUS = "complete";
@Mock
private BluetoothMapAccountItem mAccountItem;
@@ -574,7 +593,7 @@
}
@Test
- public void setSenderAddressing_withFilterMSgTypeSms_andSmsMsgTypeDraft() {
+ public void setSenderAddressing_withFilterMsgTypeSms_andSmsMsgTypeDraft() {
when(mParams.getParameterMask()).thenReturn(
(long) BluetoothMapContent.MASK_SENDER_ADDRESSING);
mInfo.mMsgType = FilterInfo.TYPE_SMS;
@@ -1089,12 +1108,12 @@
BluetoothMapConvoListing listing = mContent.convoListing(mParams, false);
assertThat(listing.getCount()).isEqualTo(2);
- BluetoothMapConvoListingElement emailElement = listing.getList().get(0);
+ BluetoothMapConvoListingElement emailElement = listing.getList().get(1);
assertThat(emailElement.getType()).isEqualTo(TYPE.EMAIL);
assertThat(emailElement.getLastActivity()).isEqualTo(TEST_DATE_EMAIL);
assertThat(emailElement.getName()).isEqualTo(TEST_NAME);
assertThat(emailElement.getReadBool()).isFalse();
- BluetoothMapConvoListingElement smsElement = listing.getList().get(1);
+ BluetoothMapConvoListingElement smsElement = listing.getList().get(0);
assertThat(smsElement.getType()).isEqualTo(TYPE.SMS_GSM);
assertThat(smsElement.getLastActivity()).isEqualTo(TEST_DATE_SMS);
assertThat(smsElement.getName()).isEqualTo("");
@@ -1156,15 +1175,310 @@
BluetoothMapConvoListing listing = mContent.convoListing(mParams, false);
assertThat(listing.getCount()).isEqualTo(2);
- BluetoothMapConvoListingElement imElement = listing.getList().get(0);
+ BluetoothMapConvoListingElement imElement = listing.getList().get(1);
assertThat(imElement.getType()).isEqualTo(TYPE.IM);
assertThat(imElement.getLastActivity()).isEqualTo(TEST_DATE_EMAIL);
assertThat(imElement.getName()).isEqualTo(TEST_NAME);
assertThat(imElement.getReadBool()).isFalse();
- BluetoothMapConvoListingElement smsElement = listing.getList().get(1);
+ BluetoothMapConvoListingElement smsElement = listing.getList().get(0);
assertThat(smsElement.getType()).isEqualTo(TYPE.SMS_GSM);
assertThat(smsElement.getLastActivity()).isEqualTo(TEST_DATE_SMS);
assertThat(smsElement.getName()).isEqualTo("");
assertThat(smsElement.getReadBool()).isTrue();
}
+
+ @Test
+ public void msgListing_withSmsCursorOnly() {
+ when(mParams.getParameterMask()).thenReturn(
+ (long) BluetoothMapAppParams.INVALID_VALUE_PARAMETER);
+ int noMms = BluetoothMapAppParams.FILTER_NO_MMS;
+ when(mParams.getFilterMessageType()).thenReturn(noMms);
+ when(mParams.getMaxListCount()).thenReturn(1);
+ when(mParams.getStartOffset()).thenReturn(0);
+
+ mCurrentFolder.setHasSmsMmsContent(true);
+ mCurrentFolder.setFolderId(TEST_ID);
+ mContent.mMsgListingVersion = BluetoothMapUtils.MAP_MESSAGE_LISTING_FORMAT_V11;
+
+ MatrixCursor smsCursor = new MatrixCursor(new String[] {BaseColumns._ID, Telephony.Sms.TYPE,
+ Telephony.Sms.READ, Telephony.Sms.BODY, Telephony.Sms.ADDRESS, Telephony.Sms.DATE,
+ Telephony.Sms.THREAD_ID, ContactsContract.Contacts.DISPLAY_NAME});
+ smsCursor.addRow(new Object[] {TEST_ID, TEST_SENT_NO, TEST_READ_TRUE, TEST_SUBJECT,
+ TEST_ADDRESS, TEST_DATE_SMS, TEST_THREAD_ID, TEST_PHONE_NAME});
+ doReturn(smsCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+ eq(BluetoothMapContent.SMS_PROJECTION), any(), any(), any());
+ doReturn(smsCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+ eq(new String[] {ContactsContract.Contacts._ID,
+ ContactsContract.Contacts.DISPLAY_NAME}), any(), any(), any());
+
+ BluetoothMapMessageListing listing = mContent.msgListing(mCurrentFolder, mParams);
+ assertThat(listing.getCount()).isEqualTo(1);
+
+ BluetoothMapMessageListingElement smsElement = listing.getList().get(0);
+ assertThat(smsElement.getHandle()).isEqualTo(TEST_ID);
+ assertThat(smsElement.getDateTime()).isEqualTo(TEST_DATE_SMS);
+ assertThat(smsElement.getType()).isEqualTo(TYPE.SMS_GSM);
+ assertThat(smsElement.getReadBool()).isTrue();
+ assertThat(smsElement.getSenderAddressing()).isEqualTo(
+ PhoneNumberUtils.extractNetworkPortion(TEST_ADDRESS));
+ assertThat(smsElement.getSenderName()).isEqualTo(TEST_PHONE_NAME);
+ assertThat(smsElement.getSize()).isEqualTo(TEST_SUBJECT.length());
+ assertThat(smsElement.getPriority()).isEqualTo(TEST_NO);
+ assertThat(smsElement.getSent()).isEqualTo(TEST_NO);
+ assertThat(smsElement.getProtect()).isEqualTo(TEST_NO);
+ assertThat(smsElement.getReceptionStatus()).isEqualTo(TEST_RECEPTION_STATUS);
+ assertThat(smsElement.getAttachmentSize()).isEqualTo(0);
+ assertThat(smsElement.getDeliveryStatus()).isEqualTo(TEST_DELIVERY_STATE);
+ }
+
+ @Test
+ public void msgListing_withMmsCursorOnly() {
+ when(mParams.getParameterMask()).thenReturn(
+ (long) BluetoothMapAppParams.INVALID_VALUE_PARAMETER);
+ int onlyMms =
+ BluetoothMapAppParams.FILTER_NO_EMAIL | BluetoothMapAppParams.FILTER_NO_SMS_CDMA
+ | BluetoothMapAppParams.FILTER_NO_SMS_GSM
+ | BluetoothMapAppParams.FILTER_NO_IM;
+ when(mParams.getFilterMessageType()).thenReturn(onlyMms);
+ when(mParams.getMaxListCount()).thenReturn(1);
+ when(mParams.getStartOffset()).thenReturn(0);
+
+ mCurrentFolder.setHasSmsMmsContent(true);
+ mCurrentFolder.setFolderId(TEST_ID);
+ mContent.mMsgListingVersion = BluetoothMapUtils.MAP_MESSAGE_LISTING_FORMAT_V11;
+
+ MatrixCursor mmsCursor = new MatrixCursor(new String[] {BaseColumns._ID,
+ Telephony.Mms.MESSAGE_BOX, Telephony.Mms.READ, Telephony.Mms.MESSAGE_SIZE,
+ Telephony.Mms.TEXT_ONLY, Telephony.Mms.DATE, Telephony.Mms.SUBJECT,
+ Telephony.Mms.THREAD_ID, Telephony.Mms.Addr.ADDRESS,
+ ContactsContract.Contacts.DISPLAY_NAME, Telephony.Mms.PRIORITY});
+ mmsCursor.addRow(new Object[] {TEST_ID, TEST_SENT_NO, TEST_READ_FALSE, TEST_SIZE,
+ TEST_TEXT_ONLY, TEST_DATE_MMS, TEST_SUBJECT, TEST_THREAD_ID, TEST_PHONE,
+ TEST_PHONE_NAME, PduHeaders.PRIORITY_HIGH});
+ doReturn(mmsCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+ eq(BluetoothMapContent.MMS_PROJECTION), any(), any(), any());
+ doReturn(mmsCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+ eq(new String[] {Telephony.Mms.Addr.ADDRESS}), any(), any(), any());
+ doReturn(mmsCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+ eq(new String[] {ContactsContract.Contacts._ID,
+ ContactsContract.Contacts.DISPLAY_NAME}), any(), any(), any());
+
+ BluetoothMapMessageListing listing = mContent.msgListing(mCurrentFolder, mParams);
+ assertThat(listing.getCount()).isEqualTo(1);
+
+ BluetoothMapMessageListingElement mmsElement = listing.getList().get(0);
+ assertThat(mmsElement.getHandle()).isEqualTo(TEST_ID);
+ assertThat(mmsElement.getDateTime()).isEqualTo(TEST_DATE_MMS * 1000L);
+ assertThat(mmsElement.getType()).isEqualTo(TYPE.MMS);
+ assertThat(mmsElement.getReadBool()).isFalse();
+ assertThat(mmsElement.getSenderAddressing()).isEqualTo(TEST_PHONE);
+ assertThat(mmsElement.getSenderName()).isEqualTo(TEST_PHONE_NAME);
+ assertThat(mmsElement.getSize()).isEqualTo(TEST_SIZE);
+ assertThat(mmsElement.getPriority()).isEqualTo(TEST_YES);
+ assertThat(mmsElement.getSent()).isEqualTo(TEST_NO);
+ assertThat(mmsElement.getProtect()).isEqualTo(TEST_NO);
+ assertThat(mmsElement.getReceptionStatus()).isEqualTo(TEST_RECEPTION_STATUS);
+ assertThat(mmsElement.getAttachmentSize()).isEqualTo(0);
+ assertThat(mmsElement.getDeliveryStatus()).isEqualTo(TEST_DELIVERY_STATE);
+ }
+
+ @Test
+ public void msgListing_withEmailCursorOnly() {
+ when(mParams.getParameterMask()).thenReturn(
+ (long) BluetoothMapAppParams.INVALID_VALUE_PARAMETER);
+ int onlyEmail =
+ BluetoothMapAppParams.FILTER_NO_MMS | BluetoothMapAppParams.FILTER_NO_SMS_CDMA
+ | BluetoothMapAppParams.FILTER_NO_SMS_GSM
+ | BluetoothMapAppParams.FILTER_NO_IM;
+ when(mParams.getFilterMessageType()).thenReturn(onlyEmail);
+ when(mParams.getMaxListCount()).thenReturn(1);
+ when(mParams.getStartOffset()).thenReturn(0);
+
+ mCurrentFolder.setHasEmailContent(true);
+ mCurrentFolder.setFolderId(TEST_ID);
+ mContent.mMsgListingVersion = BluetoothMapUtils.MAP_MESSAGE_LISTING_FORMAT_V11;
+
+ MatrixCursor emailCursor = new MatrixCursor(new String[] {
+ BluetoothMapContract.MessageColumns._ID,
+ BluetoothMapContract.MessageColumns.DATE,
+ BluetoothMapContract.MessageColumns.SUBJECT,
+ BluetoothMapContract.MessageColumns.FOLDER_ID,
+ BluetoothMapContract.MessageColumns.FLAG_READ,
+ BluetoothMapContract.MessageColumns.MESSAGE_SIZE,
+ BluetoothMapContract.MessageColumns.FROM_LIST,
+ BluetoothMapContract.MessageColumns.TO_LIST,
+ BluetoothMapContract.MessageColumns.FLAG_ATTACHMENT,
+ BluetoothMapContract.MessageColumns.ATTACHMENT_SIZE,
+ BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY,
+ BluetoothMapContract.MessageColumns.FLAG_PROTECTED,
+ BluetoothMapContract.MessageColumns.RECEPTION_STATE,
+ BluetoothMapContract.MessageColumns.DEVILERY_STATE,
+ BluetoothMapContract.MessageColumns.THREAD_ID,
+ BluetoothMapContract.MessageColumns.CC_LIST,
+ BluetoothMapContract.MessageColumns.BCC_LIST,
+ BluetoothMapContract.MessageColumns.REPLY_TO_LIST});
+ emailCursor.addRow(new Object[] {TEST_ID, TEST_DATE_EMAIL, TEST_SUBJECT, TEST_SENT_YES,
+ TEST_READ_TRUE, TEST_SIZE, TEST_FROM_ADDRESS, TEST_TO_ADDRESS, TEST_ATTACHMENT_TRUE,
+ 0, TEST_PRIORITY_HIGH, TEST_PROTECTED, 0, TEST_DELIVERY_STATE,
+ TEST_THREAD_ID, TEST_CC_ADDRESS, TEST_BCC_ADDRESS, TEST_TO_ADDRESS});
+ doReturn(emailCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+ eq(BluetoothMapContract.BT_MESSAGE_PROJECTION), any(), any(), any());
+
+ BluetoothMapMessageListing listing = mContent.msgListing(mCurrentFolder, mParams);
+ assertThat(listing.getCount()).isEqualTo(1);
+
+ BluetoothMapMessageListingElement emailElement = listing.getList().get(0);
+ assertThat(emailElement.getHandle()).isEqualTo(TEST_ID);
+ assertThat(emailElement.getDateTime()).isEqualTo(TEST_DATE_EMAIL);
+ assertThat(emailElement.getType()).isEqualTo(TYPE.EMAIL);
+ assertThat(emailElement.getReadBool()).isTrue();
+ StringBuilder expectedAddress = new StringBuilder();
+ expectedAddress.append(Rfc822Tokenizer.tokenize(TEST_FROM_ADDRESS)[0].getAddress());
+ assertThat(emailElement.getSenderAddressing()).isEqualTo(expectedAddress.toString());
+ StringBuilder expectedName = new StringBuilder();
+ expectedName.append(Rfc822Tokenizer.tokenize(TEST_FROM_ADDRESS)[0].getName());
+ assertThat(emailElement.getSenderName()).isEqualTo(expectedName.toString());
+ assertThat(emailElement.getSize()).isEqualTo(TEST_SIZE);
+ assertThat(emailElement.getPriority()).isEqualTo(TEST_YES);
+ assertThat(emailElement.getSent()).isEqualTo(TEST_YES);
+ assertThat(emailElement.getProtect()).isEqualTo(TEST_YES);
+ assertThat(emailElement.getReceptionStatus()).isEqualTo(TEST_RECEPTION_STATUS);
+ assertThat(emailElement.getAttachmentSize()).isEqualTo(TEST_SIZE);
+ assertThat(emailElement.getDeliveryStatus()).isEqualTo(TEST_DELIVERY_STATE);
+ }
+
+ @Test
+ public void msgListing_withImCursorOnly() {
+ when(mParams.getParameterMask()).thenReturn(
+ (long) BluetoothMapAppParams.INVALID_VALUE_PARAMETER);
+ int onlyIm = BluetoothMapAppParams.FILTER_NO_MMS | BluetoothMapAppParams.FILTER_NO_SMS_CDMA
+ | BluetoothMapAppParams.FILTER_NO_SMS_GSM | BluetoothMapAppParams.FILTER_NO_EMAIL;
+ when(mParams.getFilterMessageType()).thenReturn(onlyIm);
+ when(mParams.getMaxListCount()).thenReturn(1);
+ when(mParams.getStartOffset()).thenReturn(0);
+
+ mCurrentFolder.setHasImContent(true);
+ mCurrentFolder.setFolderId(TEST_ID);
+ mContent.mMsgListingVersion = BluetoothMapUtils.MAP_MESSAGE_LISTING_FORMAT_V11;
+
+ MatrixCursor imCursor = new MatrixCursor(new String[] {
+ BluetoothMapContract.MessageColumns._ID,
+ BluetoothMapContract.MessageColumns.DATE,
+ BluetoothMapContract.MessageColumns.SUBJECT,
+ BluetoothMapContract.MessageColumns.FOLDER_ID,
+ BluetoothMapContract.MessageColumns.FLAG_READ,
+ BluetoothMapContract.MessageColumns.MESSAGE_SIZE,
+ BluetoothMapContract.MessageColumns.FROM_LIST,
+ BluetoothMapContract.MessageColumns.TO_LIST,
+ BluetoothMapContract.MessageColumns.FLAG_ATTACHMENT,
+ BluetoothMapContract.MessageColumns.ATTACHMENT_SIZE,
+ BluetoothMapContract.MessageColumns.FLAG_HIGH_PRIORITY,
+ BluetoothMapContract.MessageColumns.FLAG_PROTECTED,
+ BluetoothMapContract.MessageColumns.RECEPTION_STATE,
+ BluetoothMapContract.MessageColumns.DEVILERY_STATE,
+ BluetoothMapContract.MessageColumns.THREAD_ID,
+ BluetoothMapContract.MessageColumns.THREAD_NAME,
+ BluetoothMapContract.MessageColumns.ATTACHMENT_MINE_TYPES,
+ BluetoothMapContract.MessageColumns.BODY,
+ BluetoothMapContract.ConvoContactColumns.UCI,
+ BluetoothMapContract.ConvoContactColumns.NAME});
+ imCursor.addRow(new Object[] {TEST_ID, TEST_DATE_IM, TEST_SUBJECT, TEST_SENT_NO,
+ TEST_READ_FALSE, TEST_SIZE, TEST_ID, TEST_TO_ADDRESS, TEST_ATTACHMENT_TRUE,
+ 0 /*=attachment size*/, TEST_PRIORITY_HIGH, TEST_PROTECTED, 0, TEST_DELIVERY_STATE,
+ TEST_THREAD_ID, TEST_NAME, TEST_ATTACHMENT_MIME_TYPE, 0, TEST_ADDRESS, TEST_NAME});
+ doReturn(imCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+ eq(BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION), any(), any(), any());
+ doReturn(imCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+ eq(BluetoothMapContract.BT_CONTACT_PROJECTION), any(), any(), any());
+
+ BluetoothMapMessageListing listing = mContent.msgListing(mCurrentFolder, mParams);
+ assertThat(listing.getCount()).isEqualTo(1);
+
+ BluetoothMapMessageListingElement imElement = listing.getList().get(0);
+ assertThat(imElement.getHandle()).isEqualTo(TEST_ID);
+ assertThat(imElement.getDateTime()).isEqualTo(TEST_DATE_IM);
+ assertThat(imElement.getType()).isEqualTo(TYPE.IM);
+ assertThat(imElement.getReadBool()).isFalse();
+ assertThat(imElement.getSenderAddressing()).isEqualTo(TEST_ADDRESS);
+ assertThat(imElement.getSenderName()).isEqualTo(TEST_NAME);
+ assertThat(imElement.getSize()).isEqualTo(TEST_SIZE);
+ assertThat(imElement.getPriority()).isEqualTo(TEST_YES);
+ assertThat(imElement.getSent()).isEqualTo(TEST_NO);
+ assertThat(imElement.getProtect()).isEqualTo(TEST_YES);
+ assertThat(imElement.getReceptionStatus()).isEqualTo(TEST_RECEPTION_STATUS);
+ assertThat(imElement.getAttachmentSize()).isEqualTo(TEST_SIZE);
+ assertThat(imElement.getAttachmentMimeTypes()).isEqualTo(TEST_ATTACHMENT_MIME_TYPE);
+ assertThat(imElement.getDeliveryStatus()).isEqualTo(TEST_DELIVERY_STATE);
+ assertThat(imElement.getThreadName()).isEqualTo(TEST_NAME);
+ }
+
+ @Test
+ public void msgListingSize() {
+ when(mParams.getFilterMessageType()).thenReturn(TEST_NO_FILTER);
+ mCurrentFolder.setHasSmsMmsContent(true);
+ mCurrentFolder.setHasEmailContent(true);
+ mCurrentFolder.setHasImContent(true);
+ mCurrentFolder.setFolderId(TEST_ID);
+
+ MatrixCursor smsCursor = new MatrixCursor(new String[] {"Placeholder"});
+ // Making cursor.getCount() as 1
+ smsCursor.addRow(new Object[] {1});
+ doReturn(smsCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+ eq(BluetoothMapContent.SMS_PROJECTION), any(), any(), any());
+
+ MatrixCursor mmsCursor = new MatrixCursor(new String[] {"Placeholder"});
+ // Making cursor.getCount() as 1
+ mmsCursor.addRow(new Object[] {1});
+ doReturn(mmsCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+ eq(BluetoothMapContent.MMS_PROJECTION), any(), any(), any());
+
+ MatrixCursor emailCursor = new MatrixCursor(new String[] {"Placeholder"});
+ // Making cursor.getCount() as 1
+ emailCursor.addRow(new Object[] {1});
+ doReturn(emailCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+ eq(BluetoothMapContract.BT_MESSAGE_PROJECTION), any(), any(), any());
+
+ MatrixCursor imCursor = new MatrixCursor(new String[] {"Placeholder"});
+ // Making cursor.getCount() as 1
+ imCursor.addRow(new Object[] {1});
+ doReturn(imCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+ eq(BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION), any(), any(), any());
+
+ assertThat(mContent.msgListingSize(mCurrentFolder, mParams)).isEqualTo(4);
+ }
+
+ @Test
+ public void msgListingHasUnread() {
+ when(mParams.getFilterMessageType()).thenReturn(TEST_NO_FILTER);
+ mCurrentFolder.setHasSmsMmsContent(true);
+ mCurrentFolder.setHasEmailContent(true);
+ mCurrentFolder.setHasImContent(true);
+ mCurrentFolder.setFolderId(TEST_ID);
+
+ MatrixCursor smsCursor = new MatrixCursor(new String[] {"Placeholder"});
+ // Making cursor.getCount() as 1
+ smsCursor.addRow(new Object[] {1});
+ doReturn(smsCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+ eq(BluetoothMapContent.SMS_PROJECTION), any(), any(), any());
+
+ MatrixCursor mmsCursor = new MatrixCursor(new String[] {"Placeholder"});
+ // Making cursor.getCount() as 1
+ mmsCursor.addRow(new Object[] {1});
+ doReturn(mmsCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+ eq(BluetoothMapContent.MMS_PROJECTION), any(), any(), any());
+
+ MatrixCursor emailCursor = new MatrixCursor(new String[] {"Placeholder"});
+ // Making cursor.getCount() as 1
+ emailCursor.addRow(new Object[] {1});
+ doReturn(emailCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+ eq(BluetoothMapContract.BT_MESSAGE_PROJECTION), any(), any(), any());
+
+ MatrixCursor imCursor = new MatrixCursor(new String[] {"Placeholder"});
+ // Making cursor.getCount() as 1
+ imCursor.addRow(new Object[] {1});
+ doReturn(imCursor).when(mMapMethodProxy).contentResolverQuery(any(), any(),
+ eq(BluetoothMapContract.BT_INSTANT_MESSAGE_PROJECTION), any(), any(), any());
+
+ assertThat(mContent.msgListingHasUnread(mCurrentFolder, mParams)).isTrue();
+ }
}
\ No newline at end of file
diff --git a/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapObexServerTest.java b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapObexServerTest.java
index 0a6f6bd..4b252fc 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapObexServerTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapObexServerTest.java
@@ -18,6 +18,7 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
@@ -39,6 +40,7 @@
import com.android.bluetooth.BluetoothMethodProxy;
import com.android.bluetooth.mapapi.BluetoothMapContract;
import com.android.obex.ResponseCodes;
+import com.android.obex.Operation;
import org.junit.Before;
import org.junit.Test;
@@ -185,6 +187,53 @@
assertThat(parentFolder.getFolderById(childId)).isNotNull();
}
+ @Test
+ public void setMsgTypeFilterParams_withAccountNull_andOverwriteTrue() throws Exception {
+ BluetoothMapObexServer obexServer = new BluetoothMapObexServer(null, mContext, mObserver,
+ mMasInstance, null, false);
+
+ obexServer.setMsgTypeFilterParams(mParams, true);
+
+ int expectedMask = 0;
+ expectedMask |= BluetoothMapAppParams.FILTER_NO_SMS_CDMA;
+ expectedMask |= BluetoothMapAppParams.FILTER_NO_SMS_GSM;
+ expectedMask |= BluetoothMapAppParams.FILTER_NO_MMS;
+ expectedMask |= BluetoothMapAppParams.FILTER_NO_EMAIL;
+ expectedMask |= BluetoothMapAppParams.FILTER_NO_IM;
+ assertThat(mParams.getFilterMessageType()).isEqualTo(expectedMask);
+ }
+
+ @Test
+ public void setMsgTypeFilterParams_withInvalidFilterMessageType() throws Exception {
+ BluetoothMapAccountItem accountItemWithTypeEmail = BluetoothMapAccountItem.create(TEST_ID,
+ TEST_NAME, TEST_PACKAGE_NAME, TEST_PROVIDER_AUTHORITY, TEST_DRAWABLE,
+ BluetoothMapUtils.TYPE.EMAIL, TEST_UCI, TEST_UCI_PREFIX);
+ BluetoothMapObexServer obexServer = new BluetoothMapObexServer(null, mContext, mObserver,
+ mMasInstance, accountItemWithTypeEmail, TEST_ENABLE_SMS_MMS);
+
+ // Passing mParams without any previous settings pass invalid filter message type
+ assertThrows(IllegalArgumentException.class,
+ () -> obexServer.setMsgTypeFilterParams(mParams, false));
+ }
+
+ @Test
+ public void setMsgTypeFilterParams_withValidFilterMessageType() throws Exception {
+ BluetoothMapAccountItem accountItemWithTypeIm = BluetoothMapAccountItem.create(TEST_ID,
+ TEST_NAME, TEST_PACKAGE_NAME, TEST_PROVIDER_AUTHORITY, TEST_DRAWABLE,
+ BluetoothMapUtils.TYPE.IM, TEST_UCI, TEST_UCI_PREFIX);
+ BluetoothMapObexServer obexServer = new BluetoothMapObexServer(null, mContext, mObserver,
+ mMasInstance, accountItemWithTypeIm, TEST_ENABLE_SMS_MMS);
+ int expectedMask = 1;
+ mParams.setFilterMessageType(expectedMask);
+
+ obexServer.setMsgTypeFilterParams(mParams, false);
+
+ int masFilterMask = 0;
+ masFilterMask |= BluetoothMapAppParams.FILTER_NO_EMAIL;
+ expectedMask |= masFilterMask;
+ assertThat(mParams.getFilterMessageType()).isEqualTo(expectedMask);
+ }
+
private void setUpBluetoothMapAppParams(BluetoothMapAppParams params) {
params.setPresenceAvailability(1);
params.setPresenceStatus("test_presence_status");
diff --git a/android/app/tests/unit/src/com/android/bluetooth/mapclient/MessagesFilterTest.java b/android/app/tests/unit/src/com/android/bluetooth/mapclient/MessagesFilterTest.java
new file mode 100644
index 0000000..9c6a307
--- /dev/null
+++ b/android/app/tests/unit/src/com/android/bluetooth/mapclient/MessagesFilterTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.mapclient;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class MessagesFilterTest {
+
+ @Test
+ public void setOriginator() {
+ MessagesFilter filter = new MessagesFilter();
+
+ String originator = "test_originator";
+ filter.setOriginator(originator);
+ assertThat(filter.originator).isEqualTo(originator);
+
+ filter.setOriginator("");
+ assertThat(filter.originator).isEqualTo(null); // Empty string is stored as null
+
+ filter.setOriginator(null);
+ assertThat(filter.originator).isEqualTo(null);
+ }
+
+ @Test
+ public void setPriority() {
+ MessagesFilter filter = new MessagesFilter();
+
+ byte priority = 5;
+ filter.setPriority(priority);
+
+ assertThat(filter.priority).isEqualTo(priority);
+ }
+
+ @Test
+ public void setReadStatus() {
+ MessagesFilter filter = new MessagesFilter();
+
+ byte readStatus = 5;
+ filter.setReadStatus(readStatus);
+
+ assertThat(filter.readStatus).isEqualTo(readStatus);
+ }
+
+ @Test
+ public void setRecipient() {
+ MessagesFilter filter = new MessagesFilter();
+
+ String recipient = "test_originator";
+ filter.setRecipient(recipient);
+ assertThat(filter.recipient).isEqualTo(recipient);
+
+ filter.setRecipient("");
+ assertThat(filter.recipient).isEqualTo(null); // Empty string is stored as null
+
+ filter.setRecipient(null);
+ assertThat(filter.recipient).isEqualTo(null);
+ }
+
+}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/mapclient/MessagesListingTest.java b/android/app/tests/unit/src/com/android/bluetooth/mapclient/MessagesListingTest.java
new file mode 100644
index 0000000..48ece84
--- /dev/null
+++ b/android/app/tests/unit/src/com/android/bluetooth/mapclient/MessagesListingTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.mapclient;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class MessagesListingTest {
+
+ @Test
+ public void constructor() {
+ String handle = "FFAB";
+ String subject = "test_subject";
+ final StringBuilder xml = new StringBuilder();
+ xml.append("<msg\n");
+ xml.append("handle=\"" + handle + "\"\n");
+ xml.append("subject=\"" + subject + "\"\n");
+ xml.append("/>\n");
+ ByteArrayInputStream stream = new ByteArrayInputStream(xml.toString().getBytes());
+
+ MessagesListing listing = new MessagesListing(stream);
+
+ assertThat(listing.getList()).hasSize(1);
+ Message msg = listing.getList().get(0);
+ assertThat(msg.getHandle()).isEqualTo(handle);
+ assertThat(msg.getSubject()).isEqualTo(subject);
+ }
+}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppLauncherActivityTest.java b/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppLauncherActivityTest.java
index e16e310..d9dbbb1 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppLauncherActivityTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppLauncherActivityTest.java
@@ -33,6 +33,7 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.spy;
@@ -187,7 +188,6 @@
assertThat(file.length()).isGreaterThan(shareContent.length());
}
- @Ignore("b/263754734")
@Test
public void sendFileInfo_finishImmediately() throws Exception {
doReturn(true).when(mMethodProxy).bluetoothAdapterIsEnabled(any());
@@ -195,7 +195,7 @@
mIntent.setAction("unsupported-action");
ActivityScenario<BluetoothOppLauncherActivity> scenario = ActivityScenario.launch(mIntent);
doThrow(new IllegalArgumentException()).when(mBluetoothOppManager).saveSendingFileInfo(
- any(), any(String.class), any(), any());
+ any(), any(String.class), anyBoolean(), anyBoolean());
scenario.onActivity(activity -> {
activity.sendFileInfo("text/plain", "content:///abc.txt", false, false);
});
@@ -208,18 +208,4 @@
Thread.sleep(2_000);
assertThat(activityScenario.getState()).isEqualTo(state);
}
-
-
- private void enableActivity(boolean enable) {
- int enabledState = enable ? COMPONENT_ENABLED_STATE_ENABLED
- : COMPONENT_ENABLED_STATE_DEFAULT;
-
- mTargetContext.getPackageManager().setApplicationEnabledSetting(
- mTargetContext.getPackageName(), enabledState, DONT_KILL_APP);
-
- ComponentName activityName = new ComponentName(mTargetContext,
- BluetoothOppLauncherActivity.class);
- mTargetContext.getPackageManager().setComponentEnabledSetting(
- activityName, enabledState, DONT_KILL_APP);
- }
}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppServiceTest.java
index d011a6d..96b7084 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppServiceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppServiceTest.java
@@ -15,17 +15,28 @@
*/
package com.android.bluetooth.opp;
+import static com.android.bluetooth.opp.BluetoothOppService.WHERE_INVISIBLE_UNCONFIRMED;
+
+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.anyString;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import android.app.NotificationManager;
import android.bluetooth.BluetoothAdapter;
-import android.content.Context;
+import android.content.ContentResolver;
+import android.database.MatrixCursor;
+import android.os.Handler;
-import androidx.test.filters.MediumTest;
+import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.ServiceTestRule;
import androidx.test.runner.AndroidJUnit4;
-import com.android.bluetooth.R;
+import com.android.bluetooth.BluetoothMethodProxy;
import com.android.bluetooth.TestUtils;
import com.android.bluetooth.btservice.AdapterService;
@@ -39,14 +50,14 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-@MediumTest
@RunWith(AndroidJUnit4.class)
public class BluetoothOppServiceTest {
- private BluetoothOppService mService = null;
- private BluetoothAdapter mAdapter = null;
-
@Rule
public final ServiceTestRule mServiceRule = new ServiceTestRule();
+ @Mock
+ BluetoothMethodProxy mMethodProxy;
+ private BluetoothOppService mService = null;
+ private BluetoothAdapter mAdapter = null;
@Mock
private AdapterService mAdapterService;
@@ -56,8 +67,18 @@
Assume.assumeTrue("Ignore test when BluetoothOppService is not enabled",
BluetoothOppService.isEnabled());
MockitoAnnotations.initMocks(this);
+
+ BluetoothMethodProxy.setInstanceForTesting(mMethodProxy);
+
+ // To void mockito multi-thread inter-tests problem
+ // If the thread still run in the next test, it will raise un-related mockito error
+ BluetoothOppNotification bluetoothOppNotification = mock(BluetoothOppNotification.class);
+ bluetoothOppNotification.mNotificationMgr = mock(NotificationManager.class);
+ doReturn(bluetoothOppNotification).when(mMethodProxy).newBluetoothOppNotification(any());
+
TestUtils.setAdapterService(mAdapterService);
doReturn(true, false).when(mAdapterService).isStartedProfile(anyString());
+
TestUtils.startService(mServiceRule, BluetoothOppService.class);
mService = BluetoothOppService.getBluetoothOppService();
Assert.assertNotNull(mService);
@@ -68,6 +89,7 @@
@After
public void tearDown() throws Exception {
+ BluetoothMethodProxy.setInstanceForTesting(null);
if (!BluetoothOppService.isEnabled()) {
return;
}
@@ -79,4 +101,74 @@
public void testInitialize() {
Assert.assertNotNull(BluetoothOppService.getBluetoothOppService());
}
-}
+
+ @Test
+ public void deleteShare_deleteShareAndCorrespondingBatch() {
+ int infoTimestamp = 123456789;
+ int infoTimestamp2 = 123489;
+
+ BluetoothOppShareInfo shareInfo = mock(BluetoothOppShareInfo.class);
+ shareInfo.mTimestamp = infoTimestamp;
+ shareInfo.mDestination = "AA:BB:CC:DD:EE:FF";
+ BluetoothOppShareInfo shareInfo2 = mock(BluetoothOppShareInfo.class);
+ shareInfo2.mTimestamp = infoTimestamp2;
+ shareInfo2.mDestination = "00:11:22:33:44:55";
+
+ mService.mShares.clear();
+ mService.mShares.add(shareInfo);
+ mService.mShares.add(shareInfo2);
+
+ // batch1 will be removed
+ BluetoothOppBatch batch1 = new BluetoothOppBatch(mService, shareInfo);
+ BluetoothOppBatch batch2 = new BluetoothOppBatch(mService, shareInfo2);
+ batch2.mStatus = Constants.BATCH_STATUS_FINISHED;
+ mService.mBatches.clear();
+ mService.mBatches.add(batch1);
+ mService.mBatches.add(batch2);
+
+ mService.deleteShare(0);
+ assertThat(mService.mShares.size()).isEqualTo(1);
+ assertThat(mService.mBatches.size()).isEqualTo(1);
+ assertThat(mService.mShares.get(0)).isEqualTo(shareInfo2);
+ assertThat(mService.mBatches.get(0)).isEqualTo(batch2);
+ }
+
+ @Test
+ public void dump_shouldNotThrow() {
+ BluetoothOppShareInfo info = mock(BluetoothOppShareInfo.class);
+
+ mService.mShares.add(info);
+
+ // should not throw
+ mService.dump(new StringBuilder());
+ }
+
+ @Test
+ public void trimDatabase_trimsOldOrInvisibleRecords() {
+ ContentResolver contentResolver = InstrumentationRegistry
+ .getInstrumentation().getTargetContext().getContentResolver();
+ Assume.assumeTrue("Ignore test when there is no content provider",
+ contentResolver.acquireContentProviderClient(BluetoothShare.CONTENT_URI) != null);
+
+ doReturn(1 /* any int is Ok */).when(mMethodProxy).contentResolverDelete(
+ eq(contentResolver), eq(BluetoothShare.CONTENT_URI), anyString(), any());
+
+ MatrixCursor cursor = new MatrixCursor(new String[]{BluetoothShare._ID}, 500);
+ for (long i = 0; i < Constants.MAX_RECORDS_IN_DATABASE + 20; i++) {
+ cursor.addRow(new Object[]{i});
+ }
+
+ doReturn(cursor).when(mMethodProxy).contentResolverQuery(eq(contentResolver),
+ eq(BluetoothShare.CONTENT_URI), any(), any(), any(), any());
+
+ BluetoothOppService.trimDatabase(contentResolver);
+
+ // check trimmed invisible records
+ verify(mMethodProxy).contentResolverDelete(eq(contentResolver),
+ eq(BluetoothShare.CONTENT_URI), eq(WHERE_INVISIBLE_UNCONFIRMED), any());
+
+ // check trimmed old records
+ verify(mMethodProxy).contentResolverDelete(eq(contentResolver),
+ eq(BluetoothShare.CONTENT_URI), eq(BluetoothShare._ID + " < " + 20), any());
+ }
+}
\ No newline at end of file
diff --git a/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppTransferTest.java b/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppTransferTest.java
index 35042b4..f1e2a77 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppTransferTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/opp/BluetoothOppTransferTest.java
@@ -21,6 +21,7 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doAnswer;
@@ -28,10 +29,15 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothUuid;
import android.content.ContentValues;
import android.content.Context;
import android.content.ContextWrapper;
+import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.os.Looper;
@@ -51,7 +57,8 @@
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
+
+import java.util.Objects;
@MediumTest
@RunWith(AndroidJUnit4.class)
@@ -72,8 +79,8 @@
@Mock
BluetoothOppObexSession mSession;
- @Spy
- BluetoothMethodProxy mCallProxy = BluetoothMethodProxy.getInstance();
+ @Mock
+ BluetoothMethodProxy mCallProxy;
Context mContext;
BluetoothOppBatch mBluetoothOppBatch;
BluetoothOppTransfer mTransfer;
@@ -257,4 +264,84 @@
verify(mContext).sendBroadcast(argThat(
arg -> arg.getAction().equals(BluetoothShare.USER_CONFIRMATION_TIMEOUT_ACTION)));
}
+
+ @Test
+ public void socketConnectThreadConstructors() {
+ String address = "AA:BB:CC:EE:DD:11";
+ BluetoothDevice device = (mContext.getSystemService(BluetoothManager.class))
+ .getAdapter().getRemoteDevice(address);
+ BluetoothOppTransfer transfer = new BluetoothOppTransfer(mContext, mBluetoothOppBatch);
+ BluetoothOppTransfer.SocketConnectThread socketConnectThread =
+ transfer.new SocketConnectThread(device, true);
+ BluetoothOppTransfer.SocketConnectThread socketConnectThread2 =
+ transfer.new SocketConnectThread(device, true, false, 0);
+ assertThat(Objects.equals(socketConnectThread.mDevice, device)).isTrue();
+ assertThat(Objects.equals(socketConnectThread2.mDevice, device)).isTrue();
+ }
+
+ @Test
+ public void socketConnectThreadInterrupt() {
+ String address = "AA:BB:CC:EE:DD:11";
+ BluetoothDevice device = (mContext.getSystemService(BluetoothManager.class))
+ .getAdapter().getRemoteDevice(address);
+ BluetoothOppTransfer transfer = new BluetoothOppTransfer(mContext, mBluetoothOppBatch);
+ BluetoothOppTransfer.SocketConnectThread socketConnectThread =
+ transfer.new SocketConnectThread(device, true);
+ socketConnectThread.interrupt();
+ assertThat(socketConnectThread.mIsInterrupted).isTrue();
+ }
+
+ @Test
+ @SuppressWarnings("DoNotCall")
+ public void socketConnectThreadRun_bluetoothDisabled_connectionFailed() {
+ String address = "AA:BB:CC:EE:DD:11";
+ BluetoothDevice device = (mContext.getSystemService(BluetoothManager.class))
+ .getAdapter().getRemoteDevice(address);
+ BluetoothOppTransfer transfer = new BluetoothOppTransfer(mContext, mBluetoothOppBatch);
+ BluetoothOppTransfer.SocketConnectThread socketConnectThread =
+ transfer.new SocketConnectThread(device, true);
+ transfer.mSessionHandler = mEventHandler;
+
+ socketConnectThread.run();
+ verify(mCallProxy).handlerSendEmptyMessage(any(), eq(TRANSPORT_ERROR));
+ }
+
+ @Test
+ public void oppConnectionReceiver_onReceiveWithActionAclDisconnected_sendsConnectTimeout() {
+ BluetoothDevice device = (mContext.getSystemService(BluetoothManager.class))
+ .getAdapter().getRemoteDevice(mDestination);
+ BluetoothOppTransfer transfer = new BluetoothOppTransfer(mContext, mBluetoothOppBatch);
+ transfer.mCurrentShare = mInitShareInfo;
+ transfer.mCurrentShare.mConfirm = BluetoothShare.USER_CONFIRMATION_PENDING;
+ BluetoothOppTransfer.OppConnectionReceiver receiver = transfer.new OppConnectionReceiver();
+ Intent intent = new Intent();
+ intent.setAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+
+ transfer.mSessionHandler = mEventHandler;
+ receiver.onReceive(mContext, intent);
+ verify(mCallProxy).handlerSendEmptyMessage(any(),
+ eq(BluetoothOppObexSession.MSG_CONNECT_TIMEOUT));
+ }
+
+ @Test
+ public void oppConnectionReceiver_onReceiveWithActionSdpRecord_sendsNoMessage() {
+ BluetoothDevice device = (mContext.getSystemService(BluetoothManager.class))
+ .getAdapter().getRemoteDevice(mDestination);
+ BluetoothOppTransfer transfer = new BluetoothOppTransfer(mContext, mBluetoothOppBatch);
+ transfer.mCurrentShare = mInitShareInfo;
+ transfer.mCurrentShare.mConfirm = BluetoothShare.USER_CONFIRMATION_PENDING;
+ transfer.mDevice = device;
+ transfer.mSessionHandler = mEventHandler;
+ BluetoothOppTransfer.OppConnectionReceiver receiver = transfer.new OppConnectionReceiver();
+ Intent intent = new Intent();
+ intent.setAction(BluetoothDevice.ACTION_SDP_RECORD);
+ intent.putExtra(BluetoothDevice.EXTRA_UUID, BluetoothUuid.OBEX_OBJECT_PUSH);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+
+ receiver.onReceive(mContext, intent);
+
+ // bluetooth device name is null => skip without interaction
+ verifyNoMoreInteractions(mCallProxy);
+ }
}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/telephony/BluetoothInCallServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/telephony/BluetoothInCallServiceTest.java
index 9cd29f4..9332eea 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/telephony/BluetoothInCallServiceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/telephony/BluetoothInCallServiceTest.java
@@ -1354,6 +1354,66 @@
Assert.assertFalse(mBluetoothInCallService.mOnCreateCalled);
}
+ @Test
+ public void testLeCallControlCallback_onAcceptCall_withUnknownCallId() {
+ BluetoothLeCallControlProxy callControlProxy = mock(BluetoothLeCallControlProxy.class);
+ mBluetoothInCallService.mBluetoothLeCallControl = callControlProxy;
+ BluetoothLeCallControl.Callback callback =
+ mBluetoothInCallService.mBluetoothLeCallControlCallback;
+
+ int requestId = 1;
+ UUID unknownCallId = UUID.randomUUID();
+ callback.onAcceptCall(requestId, unknownCallId);
+
+ verify(callControlProxy).requestResult(
+ requestId, BluetoothLeCallControl.RESULT_ERROR_UNKNOWN_CALL_ID);
+ }
+
+ @Test
+ public void testLeCallControlCallback_onTerminateCall_withUnknownCallId() {
+ BluetoothLeCallControlProxy callControlProxy = mock(BluetoothLeCallControlProxy.class);
+ mBluetoothInCallService.mBluetoothLeCallControl = callControlProxy;
+ BluetoothLeCallControl.Callback callback =
+ mBluetoothInCallService.mBluetoothLeCallControlCallback;
+
+ int requestId = 1;
+ UUID unknownCallId = UUID.randomUUID();
+ callback.onTerminateCall(requestId, unknownCallId);
+
+ verify(callControlProxy).requestResult(
+ requestId, BluetoothLeCallControl.RESULT_ERROR_UNKNOWN_CALL_ID);
+ }
+
+ @Test
+ public void testLeCallControlCallback_onHoldCall_withUnknownCallId() {
+ BluetoothLeCallControlProxy callControlProxy = mock(BluetoothLeCallControlProxy.class);
+ mBluetoothInCallService.mBluetoothLeCallControl = callControlProxy;
+ BluetoothLeCallControl.Callback callback =
+ mBluetoothInCallService.mBluetoothLeCallControlCallback;
+
+ int requestId = 1;
+ UUID unknownCallId = UUID.randomUUID();
+ callback.onHoldCall(requestId, unknownCallId);
+
+ verify(callControlProxy).requestResult(
+ requestId, BluetoothLeCallControl.RESULT_ERROR_UNKNOWN_CALL_ID);
+ }
+
+ @Test
+ public void testLeCallControlCallback_onUnholdCall_withUnknownCallId() {
+ BluetoothLeCallControlProxy callControlProxy = mock(BluetoothLeCallControlProxy.class);
+ mBluetoothInCallService.mBluetoothLeCallControl = callControlProxy;
+ BluetoothLeCallControl.Callback callback =
+ mBluetoothInCallService.mBluetoothLeCallControlCallback;
+
+ int requestId = 1;
+ UUID unknownCallId = UUID.randomUUID();
+ callback.onUnholdCall(requestId, unknownCallId);
+
+ verify(callControlProxy).requestResult(
+ requestId, BluetoothLeCallControl.RESULT_ERROR_UNKNOWN_CALL_ID);
+ }
+
private void addCallCapability(BluetoothCall call, int capability) {
when(call.can(capability)).thenReturn(true);
}
diff --git a/service/java/com/android/server/bluetooth/BluetoothManagerService.java b/service/java/com/android/server/bluetooth/BluetoothManagerService.java
index 2b8d66a..16e0296 100644
--- a/service/java/com/android/server/bluetooth/BluetoothManagerService.java
+++ b/service/java/com/android/server/bluetooth/BluetoothManagerService.java
@@ -302,6 +302,8 @@
// Save a ProfileServiceConnections object for each of the bound
// bluetooth profile services
private final Map<Integer, ProfileServiceConnections> mProfileServices = new HashMap<>();
+ @GuardedBy("mProfileServices")
+ private boolean mUnbindingAll = false;
private final IBluetoothCallback mBluetoothCallback = new IBluetoothCallback.Stub() {
@Override
@@ -1598,13 +1600,16 @@
} catch (IllegalArgumentException e) {
Log.e(TAG, "Unable to unbind service with intent: " + psc.mIntent, e);
}
- mProfileServices.remove(profile);
+ if (!mUnbindingAll) {
+ mProfileServices.remove(profile);
+ }
}
}
}
private void unbindAllBluetoothProfileServices() {
synchronized (mProfileServices) {
+ mUnbindingAll = true;
for (Integer i : mProfileServices.keySet()) {
ProfileServiceConnections psc = mProfileServices.get(i);
try {
@@ -1614,6 +1619,7 @@
}
psc.removeAllProxies();
}
+ mUnbindingAll = false;
mProfileServices.clear();
}
}
diff --git a/system/bta/le_audio/client.cc b/system/bta/le_audio/client.cc
index 5b1a936..c9a89bc 100644
--- a/system/bta/le_audio/client.cc
+++ b/system/bta/le_audio/client.cc
@@ -460,6 +460,8 @@
ToString(group->GetTargetState()).c_str());
group->SetTargetState(AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
+ group->PrintDebugState();
+
/* There is an issue with a setting up stream or any other operation which
* are gatt operations. It means peer is not responsable. Lets close ACL
*/
diff --git a/system/bta/le_audio/devices.cc b/system/bta/le_audio/devices.cc
index d368750..f12c923 100644
--- a/system/bta/le_audio/devices.cc
+++ b/system/bta/le_audio/devices.cc
@@ -882,16 +882,12 @@
return iter == leAudioDevices_.end();
}
-bool LeAudioDeviceGroup::HaveAllActiveDevicesCisDisc(void) {
- auto iter =
- std::find_if(leAudioDevices_.begin(), leAudioDevices_.end(), [](auto& d) {
- if (d.expired())
- return false;
- else
- return !(((d.lock()).get())->HaveAllAsesCisDisc());
- });
-
- return iter == leAudioDevices_.end();
+bool LeAudioDeviceGroup::HaveAllCisesDisconnected(void) {
+ for (auto const dev : leAudioDevices_) {
+ if (dev.expired()) continue;
+ if (dev.lock().get()->HaveAnyCisConnected()) return false;
+ }
+ return true;
}
uint8_t LeAudioDeviceGroup::GetFirstFreeCisId(void) {
@@ -2400,13 +2396,15 @@
return iter == ases_.end();
}
-bool LeAudioDevice::HaveAllAsesCisDisc(void) {
- auto iter = std::find_if(ases_.begin(), ases_.end(), [](const auto& ase) {
- return ase.active &&
- (ase.data_path_state != AudioStreamDataPathState::CIS_ASSIGNED);
- });
-
- return iter == ases_.end();
+bool LeAudioDevice::HaveAnyCisConnected(void) {
+ /* Pending and Disconnecting is considered as connected in this function */
+ for (auto const ase : ases_) {
+ if (ase.data_path_state != AudioStreamDataPathState::CIS_ASSIGNED &&
+ ase.data_path_state != AudioStreamDataPathState::IDLE) {
+ return true;
+ }
+ }
+ return false;
}
bool LeAudioDevice::HasCisId(uint8_t id) {
diff --git a/system/bta/le_audio/devices.h b/system/bta/le_audio/devices.h
index 4ffb3f4..d9f4900 100644
--- a/system/bta/le_audio/devices.h
+++ b/system/bta/le_audio/devices.h
@@ -155,7 +155,7 @@
bool IsReadyToCreateStream(void);
bool IsReadyToSuspendStream(void);
bool HaveAllActiveAsesCisEst(void);
- bool HaveAllAsesCisDisc(void);
+ bool HaveAnyCisConnected(void);
bool HasCisId(uint8_t id);
uint8_t GetMatchingBidirectionCisId(const struct types::ase* base_ase);
const struct types::acs_ac_record* GetCodecConfigurationSupportedPac(
@@ -286,7 +286,7 @@
bool IsDeviceInTheGroup(LeAudioDevice* leAudioDevice);
bool HaveAllActiveDevicesAsesTheSameState(types::AseState state);
bool IsGroupStreamReady(void);
- bool HaveAllActiveDevicesCisDisc(void);
+ bool HaveAllCisesDisconnected(void);
uint8_t GetFirstFreeCisId(void);
uint8_t GetFirstFreeCisId(types::CisType cis_type);
void CigGenerateCisIds(types::LeAudioContextType context_type);
diff --git a/system/bta/le_audio/state_machine.cc b/system/bta/le_audio/state_machine.cc
index 4530617..d891850 100644
--- a/system/bta/le_audio/state_machine.cc
+++ b/system/bta/le_audio/state_machine.cc
@@ -227,8 +227,8 @@
}
while (leAudioDevice) {
- PrepareAndSendUpdateMetadata(group, leAudioDevice,
- metadata_context_type, ccid_list);
+ PrepareAndSendUpdateMetadata(leAudioDevice, metadata_context_type,
+ ccid_list);
leAudioDevice = group->GetNextActiveDevice(leAudioDevice);
}
break;
@@ -638,7 +638,7 @@
LOG_DEBUG(
" device: %s, group connected: %d, all active ase disconnected:: %d",
leAudioDevice->address_.ToString().c_str(),
- group->IsAnyDeviceConnected(), group->HaveAllActiveDevicesCisDisc());
+ group->IsAnyDeviceConnected(), group->HaveAllCisesDisconnected());
/* Update the current group audio context availability which could change
* due to disconnected group member.
@@ -649,8 +649,7 @@
* If there is active CIS, do nothing here. Just update the available
* contexts table.
*/
- if (group->IsAnyDeviceConnected() &&
- !group->HaveAllActiveDevicesCisDisc()) {
+ if (group->IsAnyDeviceConnected() && !group->HaveAllCisesDisconnected()) {
if (group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
/* We keep streaming but want others to let know user that it might be
* need to update offloader with new CIS configuration
@@ -693,7 +692,7 @@
* or pending. If CIS is established, this will be handled in disconnected
* complete event
*/
- if (group->HaveAllActiveDevicesCisDisc()) {
+ if (group->HaveAllCisesDisconnected()) {
RemoveCigForGroup(group);
}
@@ -837,7 +836,7 @@
* If there is other device connected and streaming, just leave it as it
* is, otherwise stop the stream.
*/
- if (!group->HaveAllActiveDevicesCisDisc()) {
+ if (!group->HaveAllCisesDisconnected()) {
/* There is ASE streaming for some device. Continue streaming. */
LOG_WARN(
"Group member disconnected during streaming. Cis handle 0x%04x",
@@ -863,7 +862,7 @@
*/
if ((group->GetState() ==
AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED) &&
- group->HaveAllActiveDevicesCisDisc()) {
+ group->HaveAllCisesDisconnected()) {
/* No more transition for group */
alarm_cancel(watchdog_);
@@ -873,15 +872,58 @@
}
break;
case AseState::BTA_LE_AUDIO_ASE_STATE_IDLE:
- case AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED:
+ case AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED: {
/* Those two are used when closing the stream and CIS disconnection is
* expected */
- if (group->HaveAllActiveDevicesCisDisc()) {
- RemoveCigForGroup(group);
+ if (!group->HaveAllCisesDisconnected()) {
+ LOG_DEBUG(
+ "Still waiting for all CISes being disconnected for group:%d",
+ group->group_id_);
return;
}
- break;
+ auto current_group_state = group->GetState();
+ LOG_INFO("group %d current state: %s, target state: %s",
+ group->group_id_,
+ bluetooth::common::ToString(current_group_state).c_str(),
+ bluetooth::common::ToString(target_state).c_str());
+ /* It might happen that controller notified about CIS disconnection
+ * later, after ASE state already changed.
+ * In such an event, there is need to notify upper layer about state
+ * from here.
+ */
+ if (current_group_state == AseState::BTA_LE_AUDIO_ASE_STATE_IDLE) {
+ LOG_INFO(
+ "Cises disconnected for group %d, we are good in Idle state.",
+ group->group_id_);
+ ReleaseCisIds(group);
+ state_machine_callbacks_->StatusReportCb(group->group_id_,
+ GroupStreamStatus::IDLE);
+ } else if (current_group_state ==
+ AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED) {
+ auto reconfig = group->IsPendingConfiguration();
+ LOG_INFO(
+ "Cises disconnected for group: %d, we are good in Configured "
+ "state, reconfig=%d.",
+ group->group_id_, reconfig);
+ if (reconfig) {
+ group->ClearPendingConfiguration();
+ state_machine_callbacks_->StatusReportCb(
+ group->group_id_, GroupStreamStatus::CONFIGURED_BY_USER);
+ /* No more transition for group */
+ alarm_cancel(watchdog_);
+ } else {
+ /* This is Autonomous change if both, target and current state
+ * is CODEC_CONFIGURED
+ */
+ if (target_state == current_group_state) {
+ state_machine_callbacks_->StatusReportCb(
+ group->group_id_, GroupStreamStatus::CONFIGURED_AUTONOMOUS);
+ }
+ }
+ }
+ RemoveCigForGroup(group);
+ } break;
default:
break;
}
@@ -1468,9 +1510,21 @@
PrepareAndSendRelease(leAudioDeviceNext);
} else {
/* Last node is in releasing state*/
- if (alarm_is_scheduled(watchdog_)) alarm_cancel(watchdog_);
-
group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
+
+ group->PrintDebugState();
+ /* If all CISes are disconnected, notify upper layer about IDLE state,
+ * otherwise wait for */
+ if (!group->HaveAllCisesDisconnected()) {
+ LOG_WARN(
+ "Not all CISes removed before going to IDLE for group %d, "
+ "waiting...",
+ group->group_id_);
+ group->PrintDebugState();
+ return;
+ }
+
+ if (alarm_is_scheduled(watchdog_)) alarm_cancel(watchdog_);
ReleaseCisIds(group);
state_machine_callbacks_->StatusReportCb(group->group_id_,
GroupStreamStatus::IDLE);
@@ -1651,6 +1705,19 @@
AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED &&
group->IsPendingConfiguration()) {
LOG_INFO(" Configured state completed ");
+
+ /* If all CISes are disconnected, notify upper layer about IDLE
+ * state, otherwise wait for */
+ if (!group->HaveAllCisesDisconnected()) {
+ LOG_WARN(
+ "Not all CISes removed before going to CONFIGURED for group "
+ "%d, "
+ "waiting...",
+ group->group_id_);
+ group->PrintDebugState();
+ return;
+ }
+
group->ClearPendingConfiguration();
state_machine_callbacks_->StatusReportCb(
group->group_id_, GroupStreamStatus::CONFIGURED_BY_USER);
@@ -1789,7 +1856,6 @@
PrepareAndSendRelease(leAudioDeviceNext);
} else {
/* Last node is in releasing state*/
- if (alarm_is_scheduled(watchdog_)) alarm_cancel(watchdog_);
group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED);
/* Remote device has cache and keep staying in configured state after
@@ -1797,6 +1863,16 @@
* remote device.
*/
group->SetTargetState(group->GetState());
+
+ if (!group->HaveAllCisesDisconnected()) {
+ LOG_WARN(
+ "Not all CISes removed before going to IDLE for group %d, "
+ "waiting...",
+ group->group_id_);
+ group->PrintDebugState();
+ return;
+ }
+
state_machine_callbacks_->StatusReportCb(
group->group_id_, GroupStreamStatus::CONFIGURED_AUTONOMOUS);
}
@@ -1885,7 +1961,7 @@
group->SetState(AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED);
- if (!group->HaveAllActiveDevicesCisDisc()) return;
+ if (!group->HaveAllCisesDisconnected()) return;
if (group->GetTargetState() ==
AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED) {
@@ -2046,52 +2122,58 @@
GATT_WRITE_NO_RSP, NULL, NULL);
}
- void PrepareAndSendUpdateMetadata(LeAudioDeviceGroup* group,
- LeAudioDevice* leAudioDevice,
+ void PrepareAndSendUpdateMetadata(LeAudioDevice* leAudioDevice,
le_audio::types::AudioContexts context_type,
const std::vector<uint8_t>& ccid_list) {
std::vector<struct le_audio::client_parser::ascs::ctp_update_metadata>
confs;
- for (; leAudioDevice;
- leAudioDevice = group->GetNextActiveDevice(leAudioDevice)) {
- if (!leAudioDevice->IsMetadataChanged(context_type, ccid_list)) continue;
+ if (!leAudioDevice->IsMetadataChanged(context_type, ccid_list)) return;
- /* Request server to update ASEs with new metadata */
- for (struct ase* ase = leAudioDevice->GetFirstActiveAse(); ase != nullptr;
- ase = leAudioDevice->GetNextActiveAse(ase)) {
- LOG_DEBUG("device: %s, ase_id: %d, cis_id: %d, ase state: %s",
- leAudioDevice->address_.ToString().c_str(), ase->id,
- ase->cis_id, ToString(ase->state).c_str());
+ /* Request server to update ASEs with new metadata */
+ for (struct ase* ase = leAudioDevice->GetFirstActiveAse(); ase != nullptr;
+ ase = leAudioDevice->GetNextActiveAse(ase)) {
+ LOG_DEBUG("device: %s, ase_id: %d, cis_id: %d, ase state: %s",
+ leAudioDevice->address_.ToString().c_str(), ase->id,
+ ase->cis_id, ToString(ase->state).c_str());
- /* Filter multidirectional audio context for each ase direction */
- auto directional_audio_context =
- context_type & leAudioDevice->GetAvailableContexts(ase->direction);
- if (directional_audio_context.any()) {
- ase->metadata =
- leAudioDevice->GetMetadata(directional_audio_context, ccid_list);
- } else {
- ase->metadata = leAudioDevice->GetMetadata(
- AudioContexts(LeAudioContextType::UNSPECIFIED),
- std::vector<uint8_t>());
- }
-
- struct le_audio::client_parser::ascs::ctp_update_metadata conf;
-
- conf.ase_id = ase->id;
- conf.metadata = ase->metadata;
-
- confs.push_back(conf);
+ if (ase->state != AseState::BTA_LE_AUDIO_ASE_STATE_ENABLING &&
+ ase->state != AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
+ /* This might happen when update metadata happens on late connect */
+ LOG_DEBUG(
+ "Metadata for ase_id %d cannot be updated due to invalid ase state "
+ "- see log above",
+ ase->id);
+ continue;
}
+ /* Filter multidirectional audio context for each ase direction */
+ auto directional_audio_context =
+ context_type & leAudioDevice->GetAvailableContexts(ase->direction);
+ if (directional_audio_context.any()) {
+ ase->metadata =
+ leAudioDevice->GetMetadata(directional_audio_context, ccid_list);
+ } else {
+ ase->metadata = leAudioDevice->GetMetadata(
+ AudioContexts(LeAudioContextType::UNSPECIFIED),
+ std::vector<uint8_t>());
+ }
+
+ struct le_audio::client_parser::ascs::ctp_update_metadata conf;
+
+ conf.ase_id = ase->id;
+ conf.metadata = ase->metadata;
+
+ confs.push_back(conf);
+ }
+
+ if (confs.size() != 0) {
std::vector<uint8_t> value;
le_audio::client_parser::ascs::PrepareAseCtpUpdateMetadata(confs, value);
BtaGattQueue::WriteCharacteristic(leAudioDevice->conn_id_,
leAudioDevice->ctp_hdls_.val_hdl, value,
GATT_WRITE_NO_RSP, NULL, NULL);
-
- return;
}
}
diff --git a/system/bta/le_audio/state_machine_test.cc b/system/bta/le_audio/state_machine_test.cc
index b51937d..18e8125 100644
--- a/system/bta/le_audio/state_machine_test.cc
+++ b/system/bta/le_audio/state_machine_test.cc
@@ -2885,15 +2885,15 @@
uint8_t reason) {
bluetooth::hci::iso_manager::cis_disconnected_evt event;
- auto* ase = leAudioDevice->GetFirstActiveAse();
- while (ase) {
- event.reason = reason;
- event.cig_id = group->group_id_;
- event.cis_conn_hdl = ase->cis_conn_hdl;
- LeAudioGroupStateMachine::Get()->ProcessHciNotifCisDisconnected(
- group, leAudioDevice, &event);
-
- ase = leAudioDevice->GetNextActiveAse(ase);
+ for (auto const ase : leAudioDevice->ases_) {
+ if (ase.data_path_state != types::AudioStreamDataPathState::CIS_ASSIGNED &&
+ ase.data_path_state != types::AudioStreamDataPathState::IDLE) {
+ event.reason = reason;
+ event.cig_id = group->group_id_;
+ event.cis_conn_hdl = ase.cis_conn_hdl;
+ LeAudioGroupStateMachine::Get()->ProcessHciNotifCisDisconnected(
+ group, leAudioDevice, &event);
+ }
}
}
@@ -3332,5 +3332,253 @@
testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
}
+
+TEST_F(StateMachineTest, lateCisDisconnectedEvent_ConfiguredByUser) {
+ const auto context_type = kContextTypeMedia;
+ const auto leaudio_group_id = 6;
+ const auto num_devices = 1;
+
+ ContentControlIdKeeper::GetInstance()->SetCcid(media_context, media_ccid);
+
+ // Prepare multiple fake connected devices in a group
+ auto* group =
+ PrepareSingleTestDeviceGroup(leaudio_group_id, context_type, num_devices);
+ ASSERT_EQ(group->Size(), num_devices);
+
+ PrepareConfigureCodecHandler(group, 0, true);
+ PrepareConfigureQosHandler(group);
+ PrepareEnableHandler(group);
+ PrepareDisableHandler(group);
+ PrepareReleaseHandler(group);
+
+ auto* leAudioDevice = group->GetFirstDevice();
+ auto expected_devices_written = 0;
+
+ /* Three Writes:
+ * 1: Codec Config
+ * 2: Codec QoS
+ * 3: Enabling
+ */
+ EXPECT_CALL(gatt_queue, WriteCharacteristic(leAudioDevice->conn_id_,
+ leAudioDevice->ctp_hdls_.val_hdl,
+ _, GATT_WRITE_NO_RSP, _, _))
+ .Times(AtLeast(3));
+ expected_devices_written++;
+
+ ASSERT_EQ(expected_devices_written, num_devices);
+
+ EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1);
+ EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1);
+ EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2);
+
+ InjectInitialIdleNotification(group);
+
+ // Start the configuration and stream Media content
+ LeAudioGroupStateMachine::Get()->StartStream(
+ group, static_cast<LeAudioContextType>(context_type),
+ types::AudioContexts(context_type));
+
+ // Check if group has transitioned to a proper state
+ ASSERT_EQ(group->GetState(),
+ types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+ testing::Mock::VerifyAndClearExpectations(&mock_iso_manager_);
+
+ /* Prepare DisconnectCis mock to not symulate CisDisconnection */
+ ON_CALL(*mock_iso_manager_, DisconnectCis).WillByDefault(Return());
+
+ /* Do reconfiguration */
+ group->SetPendingConfiguration();
+
+ // Validate GroupStreamStatus
+ EXPECT_CALL(
+ mock_callbacks_,
+ StatusReportCb(leaudio_group_id,
+ bluetooth::le_audio::GroupStreamStatus::RELEASING));
+
+ EXPECT_CALL(mock_callbacks_,
+ StatusReportCb(
+ leaudio_group_id,
+ bluetooth::le_audio::GroupStreamStatus::CONFIGURED_BY_USER))
+ .Times(0);
+ LeAudioGroupStateMachine::Get()->StopStream(group);
+
+ testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
+
+ EXPECT_CALL(mock_callbacks_,
+ StatusReportCb(
+ leaudio_group_id,
+ bluetooth::le_audio::GroupStreamStatus::CONFIGURED_BY_USER));
+
+ // Inject CIS and ACL disconnection of first device
+ InjectCisDisconnected(group, leAudioDevice, HCI_ERR_CONN_CAUSE_LOCAL_HOST);
+ testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
+}
+
+TEST_F(StateMachineTest, lateCisDisconnectedEvent_AutonomousConfigured) {
+ const auto context_type = kContextTypeMedia;
+ const auto leaudio_group_id = 6;
+ const auto num_devices = 1;
+
+ ContentControlIdKeeper::GetInstance()->SetCcid(media_context, media_ccid);
+
+ // Prepare multiple fake connected devices in a group
+ auto* group =
+ PrepareSingleTestDeviceGroup(leaudio_group_id, context_type, num_devices);
+ ASSERT_EQ(group->Size(), num_devices);
+
+ PrepareConfigureCodecHandler(group, 0, true);
+ PrepareConfigureQosHandler(group);
+ PrepareEnableHandler(group);
+ PrepareDisableHandler(group);
+ PrepareReleaseHandler(group);
+
+ auto* leAudioDevice = group->GetFirstDevice();
+ auto expected_devices_written = 0;
+
+ /* Three Writes:
+ * 1: Codec Config
+ * 2: Codec QoS
+ * 3: Enabling
+ */
+ EXPECT_CALL(gatt_queue, WriteCharacteristic(leAudioDevice->conn_id_,
+ leAudioDevice->ctp_hdls_.val_hdl,
+ _, GATT_WRITE_NO_RSP, _, _))
+ .Times(AtLeast(3));
+ expected_devices_written++;
+
+ ASSERT_EQ(expected_devices_written, num_devices);
+
+ EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1);
+ EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1);
+ EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2);
+
+ InjectInitialIdleNotification(group);
+
+ // Start the configuration and stream Media content
+ LeAudioGroupStateMachine::Get()->StartStream(
+ group, static_cast<LeAudioContextType>(context_type),
+ types::AudioContexts(context_type));
+
+ // Check if group has transitioned to a proper state
+ ASSERT_EQ(group->GetState(),
+ types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+ testing::Mock::VerifyAndClearExpectations(&mock_iso_manager_);
+
+ /* Prepare DisconnectCis mock to not symulate CisDisconnection */
+ ON_CALL(*mock_iso_manager_, DisconnectCis).WillByDefault(Return());
+
+ // Validate GroupStreamStatus
+ EXPECT_CALL(
+ mock_callbacks_,
+ StatusReportCb(leaudio_group_id,
+ bluetooth::le_audio::GroupStreamStatus::RELEASING));
+
+ EXPECT_CALL(
+ mock_callbacks_,
+ StatusReportCb(
+ leaudio_group_id,
+ bluetooth::le_audio::GroupStreamStatus::CONFIGURED_AUTONOMOUS))
+ .Times(0);
+
+ // Stop the stream
+ LeAudioGroupStateMachine::Get()->StopStream(group);
+
+ // Check if group has transitioned to a proper state
+ ASSERT_EQ(group->GetState(),
+ types::AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED);
+
+ testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
+
+ EXPECT_CALL(
+ mock_callbacks_,
+ StatusReportCb(
+ leaudio_group_id,
+ bluetooth::le_audio::GroupStreamStatus::CONFIGURED_AUTONOMOUS));
+
+ // Inject CIS and ACL disconnection of first device
+ InjectCisDisconnected(group, leAudioDevice, HCI_ERR_CONN_CAUSE_LOCAL_HOST);
+ testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
+}
+
+TEST_F(StateMachineTest, lateCisDisconnectedEvent_Idle) {
+ const auto context_type = kContextTypeMedia;
+ const auto leaudio_group_id = 6;
+ const auto num_devices = 1;
+
+ ContentControlIdKeeper::GetInstance()->SetCcid(media_context, media_ccid);
+
+ // Prepare multiple fake connected devices in a group
+ auto* group =
+ PrepareSingleTestDeviceGroup(leaudio_group_id, context_type, num_devices);
+ ASSERT_EQ(group->Size(), num_devices);
+
+ PrepareConfigureCodecHandler(group);
+ PrepareConfigureQosHandler(group);
+ PrepareEnableHandler(group);
+ PrepareDisableHandler(group);
+ PrepareReleaseHandler(group);
+
+ auto* leAudioDevice = group->GetFirstDevice();
+ auto expected_devices_written = 0;
+
+ /* Three Writes:
+ * 1: Codec Config
+ * 2: Codec QoS
+ * 3: Enabling
+ */
+ EXPECT_CALL(gatt_queue, WriteCharacteristic(leAudioDevice->conn_id_,
+ leAudioDevice->ctp_hdls_.val_hdl,
+ _, GATT_WRITE_NO_RSP, _, _))
+ .Times(AtLeast(3));
+ expected_devices_written++;
+
+ ASSERT_EQ(expected_devices_written, num_devices);
+
+ EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1);
+ EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(1);
+ EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2);
+
+ InjectInitialIdleNotification(group);
+
+ // Start the configuration and stream Media content
+ LeAudioGroupStateMachine::Get()->StartStream(
+ group, static_cast<LeAudioContextType>(context_type),
+ types::AudioContexts(context_type));
+
+ // Check if group has transitioned to a proper state
+ ASSERT_EQ(group->GetState(),
+ types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+ testing::Mock::VerifyAndClearExpectations(&mock_iso_manager_);
+
+ /* Prepare DisconnectCis mock to not symulate CisDisconnection */
+ ON_CALL(*mock_iso_manager_, DisconnectCis).WillByDefault(Return());
+
+ // Validate GroupStreamStatus
+ EXPECT_CALL(
+ mock_callbacks_,
+ StatusReportCb(leaudio_group_id,
+ bluetooth::le_audio::GroupStreamStatus::RELEASING));
+
+ EXPECT_CALL(mock_callbacks_,
+ StatusReportCb(leaudio_group_id,
+ bluetooth::le_audio::GroupStreamStatus::IDLE))
+ .Times(0);
+
+ // Stop the stream
+ LeAudioGroupStateMachine::Get()->StopStream(group);
+
+ // Check if group has transitioned to a proper state
+ ASSERT_EQ(group->GetState(), types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE);
+
+ testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
+
+ EXPECT_CALL(mock_callbacks_,
+ StatusReportCb(leaudio_group_id,
+ bluetooth::le_audio::GroupStreamStatus::IDLE));
+
+ // Inject CIS and ACL disconnection of first device
+ InjectCisDisconnected(group, leAudioDevice, HCI_ERR_CONN_CAUSE_LOCAL_HOST);
+ testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
+}
} // namespace internal
} // namespace le_audio
diff --git a/system/stack/btm/btm_ble.cc b/system/stack/btm/btm_ble.cc
index 2c77945..f36a0f2 100644
--- a/system/stack/btm/btm_ble.cc
+++ b/system/stack/btm/btm_ble.cc
@@ -1607,13 +1607,30 @@
BTM_TRACE_ERROR("key size = %d", p_rec->ble.keys.key_size);
if (use_stk) {
btsnd_hcic_ble_ltk_req_reply(btm_cb.enc_handle, stk);
- } else /* calculate LTK using peer device */
- {
- if (p_rec->ble.key_type & BTM_LE_KEY_LENC)
- btsnd_hcic_ble_ltk_req_reply(btm_cb.enc_handle, p_rec->ble.keys.lltk);
- else
- btsnd_hcic_ble_ltk_req_neg_reply(btm_cb.enc_handle);
+ return;
}
+ /* calculate LTK using peer device */
+ if (p_rec->ble.key_type & BTM_LE_KEY_LENC) {
+ btsnd_hcic_ble_ltk_req_reply(btm_cb.enc_handle, p_rec->ble.keys.lltk);
+ return;
+ }
+
+ p_rec = btm_find_dev_with_lenc(bda);
+ if (!p_rec) {
+ btsnd_hcic_ble_ltk_req_neg_reply(btm_cb.enc_handle);
+ return;
+ }
+
+ LOG_INFO("Found second sec_dev_rec for device that have LTK");
+ /* This can happen when remote established LE connection using RPA to this
+ * device, but then pair with us using Classing transport while still keeping
+ * LE connection. If remote attempts to encrypt the LE connection, we might
+ * end up here. We will eventually consolidate both entries, this is to avoid
+ * race conditions. */
+
+ LOG_ASSERT(p_rec->ble.key_type & BTM_LE_KEY_LENC);
+ p_cb->key_size = p_rec->ble.keys.key_size;
+ btsnd_hcic_ble_ltk_req_reply(btm_cb.enc_handle, p_rec->ble.keys.lltk);
}
/*******************************************************************************
diff --git a/system/stack/btm/btm_dev.cc b/system/stack/btm/btm_dev.cc
index 4067e3d..5904115 100644
--- a/system/stack/btm/btm_dev.cc
+++ b/system/stack/btm/btm_dev.cc
@@ -30,6 +30,7 @@
#include <string.h>
#include "btm_api.h"
+#include "btm_ble_int.h"
#include "device/include/controller.h"
#include "l2c_api.h"
#include "main/shim/btm_api.h"
@@ -372,6 +373,32 @@
return NULL;
}
+static bool has_lenc_and_address_is_equal(void* data, void* context) {
+ tBTM_SEC_DEV_REC* p_dev_rec = static_cast<tBTM_SEC_DEV_REC*>(data);
+ if (!(p_dev_rec->ble.key_type & BTM_LE_KEY_LENC)) return false;
+
+ return is_address_equal(data, context);
+}
+
+/*******************************************************************************
+ *
+ * Function btm_find_dev_with_lenc
+ *
+ * Description Look for the record in the device database with LTK and
+ * specified BD address
+ *
+ * Returns Pointer to the record or NULL
+ *
+ ******************************************************************************/
+tBTM_SEC_DEV_REC* btm_find_dev_with_lenc(const RawAddress& bd_addr) {
+ if (btm_cb.sec_dev_rec == nullptr) return nullptr;
+
+ list_node_t* n = list_foreach(btm_cb.sec_dev_rec, has_lenc_and_address_is_equal,
+ (void*)&bd_addr);
+ if (n) return static_cast<tBTM_SEC_DEV_REC*>(list_node(n));
+
+ return NULL;
+}
/*******************************************************************************
*
* Function btm_consolidate_dev
@@ -429,6 +456,63 @@
}
}
+/* combine security records of established LE connections after Classic pairing
+ * succeeded. */
+void btm_dev_consolidate_existing_connections(const RawAddress& bd_addr) {
+ tBTM_SEC_DEV_REC* p_target_rec = btm_find_dev(bd_addr);
+ if (!p_target_rec) {
+ LOG_ERROR("No security record for just bonded device!?!?");
+ return;
+ }
+
+ if (p_target_rec->ble_hci_handle != HCI_INVALID_HANDLE) {
+ LOG_INFO("Not consolidating - already have LE connection");
+ return;
+ }
+
+ LOG_INFO("%s", PRIVATE_ADDRESS(bd_addr));
+
+ list_node_t* end = list_end(btm_cb.sec_dev_rec);
+ list_node_t* node = list_begin(btm_cb.sec_dev_rec);
+ while (node != end) {
+ tBTM_SEC_DEV_REC* p_dev_rec =
+ static_cast<tBTM_SEC_DEV_REC*>(list_node(node));
+
+ // we do list_remove in some cases, must grab next before removing
+ node = list_next(node);
+
+ if (p_target_rec == p_dev_rec) continue;
+
+ /* an RPA device entry is a duplicate of the target record */
+ if (btm_ble_addr_resolvable(p_dev_rec->bd_addr, p_target_rec)) {
+ if (p_dev_rec->ble_hci_handle == HCI_INVALID_HANDLE) {
+ LOG_INFO("already disconnected - erasing entry %s",
+ PRIVATE_ADDRESS(p_dev_rec->bd_addr));
+ wipe_secrets_and_remove(p_dev_rec);
+ continue;
+ }
+
+ LOG_INFO(
+ "Found existing LE connection to just bonded device on %s handle 0x%04x",
+ PRIVATE_ADDRESS(p_dev_rec->bd_addr), p_dev_rec->ble_hci_handle);
+
+ RawAddress ble_conn_addr = p_dev_rec->bd_addr;
+ p_target_rec->ble_hci_handle = p_dev_rec->ble_hci_handle;
+
+ /* remove the old LE record */
+ wipe_secrets_and_remove(p_dev_rec);
+
+ /* To avoid race conditions between central/peripheral starting encryption
+ * at same time, initiate it just from central. */
+ if (L2CA_GetBleConnRole(ble_conn_addr) == HCI_ROLE_CENTRAL) {
+ LOG_INFO("Will encrypt existing connection");
+ BTM_SetEncryption(ble_conn_addr, BT_TRANSPORT_LE, nullptr, nullptr,
+ BTM_BLE_SEC_ENCRYPT);
+ }
+ }
+ }
+}
+
/*******************************************************************************
*
* Function btm_find_or_alloc_dev
diff --git a/system/stack/btm/btm_dev.h b/system/stack/btm/btm_dev.h
index 9da2e89..7591129 100644
--- a/system/stack/btm/btm_dev.h
+++ b/system/stack/btm/btm_dev.h
@@ -111,8 +111,20 @@
/*******************************************************************************
*
+ * Function btm_find_dev_with_lenc
+ *
+ * Description Look for the record in the device database with LTK and
+ * specified BD address
+ *
+ * Returns Pointer to the record or NULL
+ *
+ ******************************************************************************/
+tBTM_SEC_DEV_REC* btm_find_dev_with_lenc(const RawAddress& bd_addr);
+
+/*******************************************************************************
+ *
* Function btm_consolidate_dev
-5**
+ *
* Description combine security records if identified as same peer
*
* Returns none
@@ -122,6 +134,20 @@
/*******************************************************************************
*
+ * Function btm_consolidate_dev
+ *
+ * Description When pairing is finished (i.e. on BR/EDR), this function
+ * checks if there are existing LE connections to same device
+ * that can now be encrypted and used for profiles requiring
+ * encryption.
+ *
+ * Returns none
+ *
+ ******************************************************************************/
+void btm_dev_consolidate_existing_connections(const RawAddress& bd_addr);
+
+/*******************************************************************************
+ *
* Function btm_find_or_alloc_dev
*
* Description Look for the record in the device database for the record
diff --git a/system/stack/gatt/connection_manager.cc b/system/stack/gatt/connection_manager.cc
index 5c2e880..b2f48ff 100644
--- a/system/stack/gatt/connection_manager.cc
+++ b/system/stack/gatt/connection_manager.cc
@@ -29,6 +29,7 @@
#include "bind_helpers.h"
#include "internal_include/bt_trace.h"
+#include "main/shim/dumpsys.h"
#include "main/shim/le_scanning_manager.h"
#include "main/shim/shim.h"
#include "osi/include/alarm.h"
@@ -396,6 +397,10 @@
return true;
}
+bool is_background_connection(const RawAddress& address) {
+ return bgconn_dev.find(address) != bgconn_dev.end();
+}
+
/** deregister all related background connetion device. */
void on_app_deregistered(uint8_t app_id) {
LOG_DEBUG("app_id=%d", static_cast<int>(app_id));
@@ -526,13 +531,15 @@
address.ToString().c_str());
auto it = bgconn_dev.find(address);
if (it == bgconn_dev.end()) {
- LOG_WARN("Unable to find background connection to remove");
+ LOG_WARN("Unable to find background connection to remove peer:%s",
+ PRIVATE_ADDRESS(address));
return false;
}
auto app_it = it->second.doing_direct_conn.find(app_id);
if (app_it == it->second.doing_direct_conn.end()) {
- LOG_WARN("Unable to find direct connection to remove");
+ LOG_WARN("Unable to find direct connection to remove peer:%s",
+ PRIVATE_ADDRESS(address));
return false;
}
diff --git a/system/stack/gatt/connection_manager.h b/system/stack/gatt/connection_manager.h
index 0dff252..0c407c8 100644
--- a/system/stack/gatt/connection_manager.h
+++ b/system/stack/gatt/connection_manager.h
@@ -61,4 +61,6 @@
extern void on_connection_timed_out(uint8_t app_id, const RawAddress& address);
extern void on_connection_timed_out_from_shim(const RawAddress& address);
+extern bool is_background_connection(const RawAddress& address);
+
} // namespace connection_manager
diff --git a/system/stack/gatt/gatt_utils.cc b/system/stack/gatt/gatt_utils.cc
index cd5099c..38f91a0 100644
--- a/system/stack/gatt/gatt_utils.cc
+++ b/system/stack/gatt/gatt_utils.cc
@@ -1466,11 +1466,18 @@
}
if (!connection_manager::direct_connect_remove(gatt_if, bda)) {
- BTM_AcceptlistRemove(bda);
- LOG_INFO(
- "GATT connection manager has no record but removed filter acceptlist "
- "gatt_if:%hhu peer:%s",
- gatt_if, PRIVATE_ADDRESS(bda));
+ if (!connection_manager::is_background_connection(bda)) {
+ BTM_AcceptlistRemove(bda);
+ LOG_INFO(
+ "Gatt connection manager has no background record but "
+ " removed filter acceptlist gatt_if:%hhu peer:%s",
+ gatt_if, PRIVATE_ADDRESS(bda));
+ } else {
+ LOG_INFO(
+ "Gatt connection manager maintains a background record"
+ " preserving filter acceptlist gatt_if:%hhu peer:%s",
+ gatt_if, PRIVATE_ADDRESS(bda));
+ }
}
return true;
}
diff --git a/system/stack/smp/smp_act.cc b/system/stack/smp/smp_act.cc
index dad83c0..3c8218e 100644
--- a/system/stack/smp/smp_act.cc
+++ b/system/stack/smp/smp_act.cc
@@ -1042,7 +1042,7 @@
tBTM_LE_KEY_VALUE pid_key = {
.pid_key = {},
};
- ;
+
STREAM_TO_UINT8(pid_key.pid_key.identity_addr_type, p);
STREAM_TO_BDADDR(pid_key.pid_key.identity_addr, p);
pid_key.pid_key.irk = p_cb->tk;
diff --git a/system/stack/smp/smp_utils.cc b/system/stack/smp/smp_utils.cc
index 22fcb71..71fa55d 100644
--- a/system/stack/smp/smp_utils.cc
+++ b/system/stack/smp/smp_utils.cc
@@ -41,6 +41,8 @@
#include "stack/include/stack_metrics_logging.h"
#include "types/raw_address.h"
+void btm_dev_consolidate_existing_connections(const RawAddress& bd_addr);
+
#define SMP_PAIRING_REQ_SIZE 7
#define SMP_CONFIRM_CMD_SIZE (OCTET16_LEN + 1)
#define SMP_RAND_CMD_SIZE (OCTET16_LEN + 1)
@@ -978,6 +980,10 @@
smp_status_text(evt_data.cmplt.reason).c_str()));
}
+ if (p_cb->status == SMP_SUCCESS && p_cb->smp_over_br) {
+ btm_dev_consolidate_existing_connections(pairing_bda);
+ }
+
smp_reset_control_value(p_cb);
if (p_callback) (*p_callback)(SMP_COMPLT_EVT, pairing_bda, &evt_data);
diff --git a/system/stack/test/gatt/gatt_sr_test.cc b/system/stack/test/gatt/gatt_sr_test.cc
index 7e6ab31..f142bae 100644
--- a/system/stack/test/gatt/gatt_sr_test.cc
+++ b/system/stack/test/gatt/gatt_sr_test.cc
@@ -63,6 +63,8 @@
bool direct_connect_remove(uint8_t app_id, const RawAddress& address) {
return false;
}
+bool is_background_connection(const RawAddress& address) { return false; }
+
} // namespace connection_manager
BT_HDR* attp_build_sr_msg(tGATT_TCB& tcb, uint8_t op_code, tGATT_SR_MSG* p_msg,
diff --git a/system/stack/test/gatt/mock_gatt_utils_ref.cc b/system/stack/test/gatt/mock_gatt_utils_ref.cc
index d416e98..59d0022 100644
--- a/system/stack/test/gatt/mock_gatt_utils_ref.cc
+++ b/system/stack/test/gatt/mock_gatt_utils_ref.cc
@@ -29,6 +29,7 @@
bool direct_connect_remove(uint8_t app_id, const RawAddress& address) {
return false;
}
+bool is_background_connection(const RawAddress& address) { return false; }
} // namespace connection_manager
/** stack/gatt/att_protocol.cc */
diff --git a/system/test/mock/mock_stack_btm_dev.cc b/system/test/mock/mock_stack_btm_dev.cc
index a01217b..8a65ab8 100644
--- a/system/test/mock/mock_stack_btm_dev.cc
+++ b/system/test/mock/mock_stack_btm_dev.cc
@@ -109,3 +109,6 @@
void wipe_secrets_and_remove(tBTM_SEC_DEV_REC* p_dev_rec) {
mock_function_count_map[__func__]++;
}
+void btm_dev_consolidate_existing_connections(const RawAddress& bd_addr) {
+ mock_function_count_map[__func__]++;
+}
diff --git a/system/test/mock/mock_stack_gatt_connection_manager.cc b/system/test/mock/mock_stack_gatt_connection_manager.cc
index 99f2904..372b4f0 100644
--- a/system/test/mock/mock_stack_gatt_connection_manager.cc
+++ b/system/test/mock/mock_stack_gatt_connection_manager.cc
@@ -92,3 +92,8 @@
void connection_manager::reset(bool after_reset) {
mock_function_count_map[__func__]++;
}
+
+bool connection_manager::is_background_connection(const RawAddress& address) {
+ mock_function_count_map[__func__]++;
+ return false;
+}