Loading android/app/jni/com_android_bluetooth_sdp.cpp +55 −2 Original line number Diff line number Diff line Loading @@ -35,7 +35,8 @@ static const uint8_t UUID_MAP_MNS[] = {0x00, 0x00, 0x11, 0x33, 0x00, 0x00, 0x10 0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB}; static const uint8_t UUID_SPP[] = {0x00, 0x00, 0x11, 0x01, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB}; static const uint8_t UUID_SAP[] = {0x00, 0x00, 0x11, 0x2D, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB}; // TODO: // Both the fact that the UUIDs are declared in multiple places, plus the fact // that there is a mess of UUID comparison and shortening methods will have to Loading @@ -52,6 +53,7 @@ static jmethodID method_sdpMasRecordFoundCallback; static jmethodID method_sdpMnsRecordFoundCallback; static jmethodID method_sdpPseRecordFoundCallback; static jmethodID method_sdpOppOpsRecordFoundCallback; static jmethodID method_sdpSapsRecordFoundCallback; static const btsdp_interface_t *sBluetoothSdpInterface = NULL; Loading Loading @@ -123,6 +125,10 @@ static void classInitNative(JNIEnv* env, jclass clazz) { method_sdpOppOpsRecordFoundCallback = env->GetMethodID(clazz, "sdpOppOpsRecordFoundCallback", "(I[B[BIIILjava/lang/String;[BZ)V"); /* SAP Server record */ method_sdpSapsRecordFoundCallback = env->GetMethodID(clazz, "sdpSapsRecordFoundCallback", "(I[B[BIILjava/lang/String;Z)V"); } Loading Loading @@ -261,6 +267,15 @@ static void sdp_search_callback(bt_status_t status, bt_bdaddr_t *bd_addr, uint8_ more_results); sCallbackEnv->DeleteLocalRef(formats_list); } else if (IS_UUID(UUID_SAP, uuid_in)) { sCallbackEnv->CallVoidMethod(sCallbacksObj, method_sdpSapsRecordFoundCallback, (jint) status, addr, uuid, (jint)record->mas.hdr.rfcomm_channel_number, (jint)record->mas.hdr.profile_version, service_name, more_results); } else { // we don't have a wrapper for this uuid, send as raw data jint record_data_size = record->hdr.user1_ptr_len; Loading Loading @@ -467,6 +482,42 @@ static jint sdpCreateOppOpsRecordNative(JNIEnv *env, jobject obj, jstring name_s return handle; } static jint sdpCreateSapsRecordNative(JNIEnv *env, jobject obj, jstring name_str, jint scn, jint version) { ALOGD("%s:",__FUNCTION__); const char* service_name = NULL; bluetooth_sdp_record record = {}; // Must be zero initialized int handle = -1; int ret = 0; if (!sBluetoothSdpInterface) return handle; record.sap.hdr.type = SDP_TYPE_SAP_SERVER; if (name_str != NULL) { service_name = env->GetStringUTFChars(name_str, NULL); record.mas.hdr.service_name = (char *) service_name; record.mas.hdr.service_name_length = strlen(service_name); } else { record.mas.hdr.service_name = NULL; record.mas.hdr.service_name_length = 0; } record.mas.hdr.rfcomm_channel_number = scn; record.mas.hdr.profile_version = version; if ( (ret = sBluetoothSdpInterface->create_sdp_record(&record, &handle)) != BT_STATUS_SUCCESS) { ALOGE("SDP Create record failed: %d", ret); goto Fail; } ALOGD("SDP Create record success - handle: %d", handle); Fail: if (service_name) env->ReleaseStringUTFChars(name_str, service_name); return handle; } static jboolean sdpRemoveSdpRecordNative(JNIEnv *env, jobject obj, jint record_id) { ALOGD("%s:",__FUNCTION__); Loading Loading @@ -520,6 +571,8 @@ static JNINativeMethod sMethods[] = { (void*) sdpCreatePbapPseRecordNative}, {"sdpCreateOppOpsRecordNative", "(Ljava/lang/String;III[B)I", (void*) sdpCreateOppOpsRecordNative}, {"sdpCreateSapsRecordNative", "(Ljava/lang/String;II)I", (void*) sdpCreateSapsRecordNative}, {"sdpRemoveSdpRecordNative", "(I)Z", (void*) sdpRemoveSdpRecordNative} }; Loading android/app/src/com/android/bluetooth/sap/SapService.java +18 −2 Original line number Diff line number Diff line Loading @@ -37,10 +37,13 @@ import com.android.bluetooth.Utils; import com.android.bluetooth.btservice.AdapterService; import com.android.bluetooth.btservice.ProfileService; import com.android.bluetooth.btservice.ProfileService.IProfileServiceBinder; import com.android.bluetooth.sdp.SdpManager; @TargetApi(Build.VERSION_CODES.ECLAIR) public class SapService extends ProfileService { private static final String SDP_SAP_SERVICE_NAME = "SIM Access"; private static final int SDP_SAP_VERSION = 0x0102; private static final String TAG = "SapService"; public static final boolean DEBUG = true; public static final boolean VERBOSE = true; Loading @@ -64,6 +67,7 @@ public class SapService extends ProfileService { private BluetoothAdapter mAdapter; private SocketAcceptThread mAcceptThread = null; private BluetoothServerSocket mServerSocket = null; private int mSdpHandle = -1; private BluetoothSocket mConnSocket = null; private BluetoothDevice mRemoteDevice = null; private static String sRemoteDeviceName = null; Loading Loading @@ -109,10 +113,22 @@ public class SapService extends ProfileService { for (int i = 0; i < CREATE_RETRY_TIME && !mInterrupted; i++) { initSocketOK = true; try { // // It is mandatory for MSE to support initiation of bonding and // encryption. mServerSocket = mAdapter.listenUsingRfcommWithServiceRecord ("SIM Access", BluetoothUuid.SAP.getUuid()); // // TODO: Consider reusing the mServerSocket - it is intended // to be reused for multiple connections. // mServerSocket = mAdapter. listenUsingRfcommOn(BluetoothAdapter.SOCKET_CHANNEL_AUTO_STATIC_NO_SDP); int channel = mServerSocket.getChannel(); if (mSdpHandle >= 0) { SdpManager.getDefaultManager().removeSdpRecord(mSdpHandle); if (VERBOSE) Log.d(TAG, "Removing SDP record"); } mSdpHandle = SdpManager.getDefaultManager().createSapsRecord(SDP_SAP_SERVICE_NAME, mServerSocket.getChannel(), SDP_SAP_VERSION); } catch (IOException e) { Log.e(TAG, "Error create RfcommServerSocket ", e); Loading android/app/src/com/android/bluetooth/sdp/SdpManager.java +51 −0 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ import android.bluetooth.SdpMasRecord; import android.bluetooth.SdpMnsRecord; import android.bluetooth.SdpOppOpsRecord; import android.bluetooth.SdpPseRecord; import android.bluetooth.SdpSapsRecord; import android.bluetooth.SdpRecord; import android.content.Intent; import android.os.Handler; Loading Loading @@ -107,6 +108,9 @@ public class SdpManager { private native int sdpCreateOppOpsRecordNative(String serviceName, int rfcommChannel, int l2capPsm, int version, byte[] formats_list); private native int sdpCreateSapsRecordNative(String serviceName, int rfcommChannel, int version); private native boolean sdpRemoveSdpRecordNative(int record_id); Loading Loading @@ -364,6 +368,31 @@ public class SdpManager { } } void sdpSapsRecordFoundCallback(int status, byte[] address, byte[] uuid, int rfcommCannelNumber, int profileVersion, String serviceName, boolean moreResults) { synchronized(mTrackerLock) { SdpSearchInstance inst = sSdpSearchTracker.getSearchInstance(address, uuid); SdpSapsRecord sdpRecord = null; if (inst == null) { Log.e(TAG, "sdpSapsRecordFoundCallback: Search instance is NULL"); return; } inst.setStatus(status); if (status == AbstractionLayer.BT_STATUS_SUCCESS) { sdpRecord = new SdpSapsRecord(rfcommCannelNumber, profileVersion, serviceName); } if (D) Log.d(TAG, "UUID: " + Arrays.toString(uuid)); if (D) Log.d(TAG, "UUID in parcel: " + ((Utils.byteArrayToUuid(uuid))[0]).toString()); sendSdpIntent(inst, sdpRecord, moreResults); } } /* TODO: Test or remove! */ void sdpRecordFoundCallback(int status, byte[] address, byte[] uuid, int size_record, byte[] record) { Loading Loading @@ -591,6 +620,28 @@ public class SdpManager { l2capPsm, version, formatsList); } /** * Create a server side Sim Access Profile Service Record. * Create the record once, and reuse it for all connections. * If changes to a record is needed remove the old record using {@link removeSdpRecord} * and then create a new one. * @param serviceName The textual name of the service * @param rfcommChannel The RFCOMM channel that clients can connect to * (obtain from BluetoothServerSocket) * @param version The Profile version number (As specified in the Bluetooth * SAP specification) * @return a handle to the record created. The record can be removed again * using {@link removeSdpRecord}(). The record is not linked to the * creation/destruction of BluetoothSockets, hence SDP record cleanup * is a separate process. */ public int createSapsRecord(String serviceName, int rfcommChannel, int version) { if (sNativeAvailable == false) { throw new RuntimeException(TAG + " sNativeAvailable == false - native not initialized"); } return sdpCreateSapsRecordNative(serviceName, rfcommChannel, version); } /** * Remove a SDP record. * When Bluetooth is disabled all records will be deleted, hence there Loading android/app/tests/src/com/android/bluetooth/tests/SapServerTest.java +56 −31 Original line number Diff line number Diff line Loading @@ -30,8 +30,9 @@ import com.android.bluetooth.sap.SapServer; public class SapServerTest extends AndroidTestCase { protected static String TAG = "SapServerTest"; protected static final boolean D = true; private static final boolean rilTestModeEnabled = false; /* Set the RIL driver in test mode, where request stubs are used in stead of forwarding to the modem/sim */ // Set the RIL driver in test mode, where request stubs are used instead // of forwarding to the Modem/SIM. private static final boolean rilTestModeEnabled = false; private Context mContext = null; public SapServerTest() { Loading Loading @@ -176,16 +177,20 @@ public class SapServerTest extends AndroidTestCase { } sequencer.addStep(apdu7816Req, apdu7816Resp); SapMessage transferCardReaderStatusReq = new SapMessage(SapMessage.ID_TRANSFER_CARD_READER_STATUS_REQ); SapMessage transferCardReaderStatusReq = new SapMessage(SapMessage.ID_TRANSFER_CARD_READER_STATUS_REQ); SapMessage transferCardReaderStatusResp = new SapMessage(SapMessage.ID_TRANSFER_CARD_READER_STATUS_RESP); SapMessage transferCardReaderStatusResp = new SapMessage(SapMessage.ID_TRANSFER_CARD_READER_STATUS_RESP); transferCardReaderStatusResp.setResultCode(SapMessage.RESULT_OK); sequencer.addStep(transferCardReaderStatusReq, transferCardReaderStatusResp); SapMessage setTransportProtocolReq = new SapMessage(SapMessage.ID_SET_TRANSPORT_PROTOCOL_REQ); SapMessage setTransportProtocolReq = new SapMessage(SapMessage.ID_SET_TRANSPORT_PROTOCOL_REQ); setTransportProtocolReq.setTransportProtocol(0x01); // T=1 SapMessage setTransportProtocolResp = new SapMessage(SapMessage.ID_SET_TRANSPORT_PROTOCOL_RESP); SapMessage setTransportProtocolResp = new SapMessage(SapMessage.ID_SET_TRANSPORT_PROTOCOL_RESP); setTransportProtocolResp.setResultCode(SapMessage.RESULT_OK); sequencer.addStep(setTransportProtocolReq, setTransportProtocolResp); Loading Loading @@ -222,8 +227,12 @@ public class SapServerTest extends AndroidTestCase { SapMessage apduReq = new SapMessage(SapMessage.ID_TRANSFER_APDU_REQ); apduReq.setApdu(dummyBytes); SapMessage apduResp = null; /* expect no response as we send a SIM_RESET before the write APDU completes TODO: Consider adding a real response, and add an optional flag. */ // // Expect no response as we send a SIM_RESET before the write APDU // completes. // TODO: Consider adding a real response, and add an optional flag. // SapMessage apduResp = null; index = sequencer.addStep(apduReq, apduResp); SapMessage resetReq = new SapMessage(SapMessage.ID_RESET_SIM_REQ); Loading @@ -249,7 +258,8 @@ public class SapServerTest extends AndroidTestCase { */ public void testSapServerTimeouts() { Intent sapDisconnectIntent = new Intent(SapServer.SAP_DISCONNECT_ACTION); sapDisconnectIntent.putExtra(SapServer.SAP_DISCONNECT_TYPE_EXTRA, SapMessage.DISC_IMMEDIATE); sapDisconnectIntent.putExtra( SapServer.SAP_DISCONNECT_TYPE_EXTRA, SapMessage.DISC_IMMEDIATE); mContext = this.getContext(); try { Loading Loading @@ -298,7 +308,8 @@ public class SapServerTest extends AndroidTestCase { public class SeqStep { public ArrayList<SapMessage> requests = null; public ArrayList<SapMessage> responses = null; public int index = 0; /* requests with same index are executed in parallel (without waiting for a response) */ public int index = 0; // Requests with same index are executed in // parallel without waiting for a response public SeqStep(SapMessage request, SapMessage response) { requests = new ArrayList<SapMessage>(); responses = new ArrayList<SapMessage>(); Loading Loading @@ -328,7 +339,8 @@ public class SapServerTest extends AndroidTestCase { public SapSequencer() throws IOException { /* Setup the looper thread to handle messages */ handlerThread = new HandlerThread("SapTestTimeoutHandler", android.os.Process.THREAD_PRIORITY_BACKGROUND); handlerThread = new HandlerThread("SapTestTimeoutHandler", android.os.Process.THREAD_PRIORITY_BACKGROUND); handlerThread.start(); Looper testLooper = handlerThread.getLooper(); messageHandler = new Handler(testLooper, this); Loading @@ -339,7 +351,8 @@ public class SapServerTest extends AndroidTestCase { /* Create a SapServer. Fake the BtSocket using piped input/output streams*/ inStream = new PipedInputStream(8092); outStream = new PipedOutputStream(); sapServer = new SapServer(null, mContext, new PipedInputStream(outStream, 8092), new PipedOutputStream(inStream)); sapServer = new SapServer(null, mContext, new PipedInputStream(outStream, 8092), new PipedOutputStream(inStream)); sapServer.start(); } Loading @@ -364,15 +377,16 @@ public class SapServerTest extends AndroidTestCase { * @param response The response to EXPECT from the SAP server * @return The created step index, which can be used when adding events or actions. */ public int addStep(SapMessage request, SapMessage response) { // TODO: should we add a step trigger? (in stead of just executing in sequence) public int addStep(SapMessage request, SapMessage response) { // TODO: should we add a step trigger? (in stead of just executing in sequence) SeqStep newStep = new SeqStep(request, response); sequence.add(newStep); return sequence.indexOf(newStep); } /** * Add a sub-step to a sequencer step. All requests added to the same index will be send to the * SapServer in the order added before listening for the response. * Add a sub-step to a sequencer step. All requests added to the same index will be send to * the SapServer in the order added before listening for the response. * The response order is not validated - hence for each response received the entire list of * responses in the step will be searched for a match. * @param index the index returned from addStep() to which the sub-step is to be added. Loading @@ -399,8 +413,9 @@ public class SapServerTest extends AndroidTestCase { if(step.requests != null) { for(SapMessage request : step.requests) { if(request != null) { Log.i(TAG, "Writing request: " + SapMessage.getMsgTypeName(request.getMsgType())); writeSapMessage(request, false); /* write the message without flushing */ Log.i(TAG, "Writing request: " + SapMessage.getMsgTypeName(request.getMsgType())); writeSapMessage(request, false); // write the message without flushing } } writeSapMessage(null, true); /* flush the pipe */ Loading @@ -414,11 +429,13 @@ public class SapServerTest extends AndroidTestCase { while(!done) { for(SapMessage response : step.responses) { if(response != null) Log.i(TAG, "Waiting for the response: " + SapMessage.getMsgTypeName(response.getMsgType())); Log.i(TAG, "Waiting for the response: " + SapMessage.getMsgTypeName(response.getMsgType())); } inMsg = readSapMessage(); if(inMsg != null) Log.i(TAG, "Read message: " + SapMessage.getMsgTypeName(inMsg.getMsgType())); Log.i(TAG, "Read message: " + SapMessage.getMsgTypeName(inMsg.getMsgType())); else assertTrue("Failed to read message.", false); Loading Loading @@ -465,7 +482,8 @@ public class SapServerTest extends AndroidTestCase { */ private boolean compareSapMessages(SapMessage received, SapMessage expected) { boolean retVal = true; if(expected.getCardReaderStatus() != -1 && received.getCardReaderStatus() != expected.getCardReaderStatus()) { if(expected.getCardReaderStatus() != -1 && received.getCardReaderStatus() != expected.getCardReaderStatus()) { Log.i(TAG, "received.getCardReaderStatus() != expected.getCardReaderStatus() " + received.getCardReaderStatus() + " != " + expected.getCardReaderStatus()); retVal = false; Loading @@ -477,7 +495,8 @@ public class SapServerTest extends AndroidTestCase { } if(received.getDisconnectionType() != expected.getDisconnectionType()) { Log.i(TAG, "received.getDisconnectionType() != expected.getDisconnectionType() " + received.getDisconnectionType() +" != " + expected.getDisconnectionType()); + received.getDisconnectionType() + " != " + expected.getDisconnectionType()); retVal = false; } if(received.getMaxMsgSize() != expected.getMaxMsgSize()) { Loading @@ -502,27 +521,33 @@ public class SapServerTest extends AndroidTestCase { } if(received.getTransportProtocol() != expected.getTransportProtocol()) { Log.i(TAG, "received.getTransportProtocol() != expected.getTransportProtocol() " + received.getTransportProtocol() +" != " + expected.getTransportProtocol()); + received.getTransportProtocol() + " != " + expected.getTransportProtocol()); retVal = false; } if(!Arrays.equals(received.getApdu(), expected.getApdu())) { Log.i(TAG, "received.getApdu() != expected.getApdu() " + Arrays.toString(received.getApdu()) +" != " + Arrays.toString(expected.getApdu())); + Arrays.toString(received.getApdu()) + " != " + Arrays.toString(expected.getApdu())); retVal = false; } if(!Arrays.equals(received.getApdu7816(), expected.getApdu7816())) { Log.i(TAG, "received.getApdu7816() != expected.getApdu7816() " + Arrays.toString(received.getApdu7816()) +" != " + Arrays.toString(expected.getApdu7816())); + Arrays.toString(received.getApdu7816()) + " != " + Arrays.toString(expected.getApdu7816())); retVal = false; } if(expected.getApduResp() != null && !Arrays.equals(received.getApduResp(), expected.getApduResp())) { if(expected.getApduResp() != null && !Arrays.equals(received.getApduResp(), expected.getApduResp())) { Log.i(TAG, "received.getApduResp() != expected.getApduResp() " + Arrays.toString(received.getApduResp()) +" != " + Arrays.toString(expected.getApduResp())); + Arrays.toString(received.getApduResp()) + " != " + Arrays.toString(expected.getApduResp())); retVal = false; } if(expected.getAtr() != null && !Arrays.equals(received.getAtr(), expected.getAtr())) { Log.i(TAG, "received.getAtr() != expected.getAtr() " + Arrays.toString(received.getAtr()) +" != " + Arrays.toString(expected.getAtr())); + Arrays.toString(received.getAtr()) + " != " + Arrays.toString(expected.getAtr())); retVal = false; } return retVal; Loading android/app/tests/src/com/android/bluetooth/tests/SdpManagerTest.java +8 −1 Original line number Diff line number Diff line Loading @@ -110,6 +110,12 @@ public class SdpManagerTest extends AndroidTestCase { Log.i(TAG, " Added record_handle=" + handles[record_id]); assertTrue(handles[record_id]>=0); if(record_id == count) break; handles[++record_id] = mManager.createSapsRecord(SDP_SERVER_NAME, record_id, SDP_VERSION); Log.i(TAG, " Added record_handle=" + handles[record_id]); assertTrue(handles[record_id]>=0); if (record_id == count) break; } } Loading Loading @@ -159,7 +165,8 @@ public class SdpManagerTest extends AndroidTestCase { final String[] uuids = {BluetoothUuid.MAS.toString(), BluetoothUuid.MNS.toString(), BluetoothUuid.PBAP_PSE.toString(), BluetoothUuid.ObexObjectPush.toString()}; BluetoothUuid.ObexObjectPush.toString(), BluetoothUuid.SAP.toString()}; final String uuids_str; final StringBuilder sb = new StringBuilder(uuids.length*2-1); for(String str : uuids) { Loading Loading
android/app/jni/com_android_bluetooth_sdp.cpp +55 −2 Original line number Diff line number Diff line Loading @@ -35,7 +35,8 @@ static const uint8_t UUID_MAP_MNS[] = {0x00, 0x00, 0x11, 0x33, 0x00, 0x00, 0x10 0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB}; static const uint8_t UUID_SPP[] = {0x00, 0x00, 0x11, 0x01, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB}; static const uint8_t UUID_SAP[] = {0x00, 0x00, 0x11, 0x2D, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0x80, 0x5F, 0x9B, 0x34, 0xFB}; // TODO: // Both the fact that the UUIDs are declared in multiple places, plus the fact // that there is a mess of UUID comparison and shortening methods will have to Loading @@ -52,6 +53,7 @@ static jmethodID method_sdpMasRecordFoundCallback; static jmethodID method_sdpMnsRecordFoundCallback; static jmethodID method_sdpPseRecordFoundCallback; static jmethodID method_sdpOppOpsRecordFoundCallback; static jmethodID method_sdpSapsRecordFoundCallback; static const btsdp_interface_t *sBluetoothSdpInterface = NULL; Loading Loading @@ -123,6 +125,10 @@ static void classInitNative(JNIEnv* env, jclass clazz) { method_sdpOppOpsRecordFoundCallback = env->GetMethodID(clazz, "sdpOppOpsRecordFoundCallback", "(I[B[BIIILjava/lang/String;[BZ)V"); /* SAP Server record */ method_sdpSapsRecordFoundCallback = env->GetMethodID(clazz, "sdpSapsRecordFoundCallback", "(I[B[BIILjava/lang/String;Z)V"); } Loading Loading @@ -261,6 +267,15 @@ static void sdp_search_callback(bt_status_t status, bt_bdaddr_t *bd_addr, uint8_ more_results); sCallbackEnv->DeleteLocalRef(formats_list); } else if (IS_UUID(UUID_SAP, uuid_in)) { sCallbackEnv->CallVoidMethod(sCallbacksObj, method_sdpSapsRecordFoundCallback, (jint) status, addr, uuid, (jint)record->mas.hdr.rfcomm_channel_number, (jint)record->mas.hdr.profile_version, service_name, more_results); } else { // we don't have a wrapper for this uuid, send as raw data jint record_data_size = record->hdr.user1_ptr_len; Loading Loading @@ -467,6 +482,42 @@ static jint sdpCreateOppOpsRecordNative(JNIEnv *env, jobject obj, jstring name_s return handle; } static jint sdpCreateSapsRecordNative(JNIEnv *env, jobject obj, jstring name_str, jint scn, jint version) { ALOGD("%s:",__FUNCTION__); const char* service_name = NULL; bluetooth_sdp_record record = {}; // Must be zero initialized int handle = -1; int ret = 0; if (!sBluetoothSdpInterface) return handle; record.sap.hdr.type = SDP_TYPE_SAP_SERVER; if (name_str != NULL) { service_name = env->GetStringUTFChars(name_str, NULL); record.mas.hdr.service_name = (char *) service_name; record.mas.hdr.service_name_length = strlen(service_name); } else { record.mas.hdr.service_name = NULL; record.mas.hdr.service_name_length = 0; } record.mas.hdr.rfcomm_channel_number = scn; record.mas.hdr.profile_version = version; if ( (ret = sBluetoothSdpInterface->create_sdp_record(&record, &handle)) != BT_STATUS_SUCCESS) { ALOGE("SDP Create record failed: %d", ret); goto Fail; } ALOGD("SDP Create record success - handle: %d", handle); Fail: if (service_name) env->ReleaseStringUTFChars(name_str, service_name); return handle; } static jboolean sdpRemoveSdpRecordNative(JNIEnv *env, jobject obj, jint record_id) { ALOGD("%s:",__FUNCTION__); Loading Loading @@ -520,6 +571,8 @@ static JNINativeMethod sMethods[] = { (void*) sdpCreatePbapPseRecordNative}, {"sdpCreateOppOpsRecordNative", "(Ljava/lang/String;III[B)I", (void*) sdpCreateOppOpsRecordNative}, {"sdpCreateSapsRecordNative", "(Ljava/lang/String;II)I", (void*) sdpCreateSapsRecordNative}, {"sdpRemoveSdpRecordNative", "(I)Z", (void*) sdpRemoveSdpRecordNative} }; Loading
android/app/src/com/android/bluetooth/sap/SapService.java +18 −2 Original line number Diff line number Diff line Loading @@ -37,10 +37,13 @@ import com.android.bluetooth.Utils; import com.android.bluetooth.btservice.AdapterService; import com.android.bluetooth.btservice.ProfileService; import com.android.bluetooth.btservice.ProfileService.IProfileServiceBinder; import com.android.bluetooth.sdp.SdpManager; @TargetApi(Build.VERSION_CODES.ECLAIR) public class SapService extends ProfileService { private static final String SDP_SAP_SERVICE_NAME = "SIM Access"; private static final int SDP_SAP_VERSION = 0x0102; private static final String TAG = "SapService"; public static final boolean DEBUG = true; public static final boolean VERBOSE = true; Loading @@ -64,6 +67,7 @@ public class SapService extends ProfileService { private BluetoothAdapter mAdapter; private SocketAcceptThread mAcceptThread = null; private BluetoothServerSocket mServerSocket = null; private int mSdpHandle = -1; private BluetoothSocket mConnSocket = null; private BluetoothDevice mRemoteDevice = null; private static String sRemoteDeviceName = null; Loading Loading @@ -109,10 +113,22 @@ public class SapService extends ProfileService { for (int i = 0; i < CREATE_RETRY_TIME && !mInterrupted; i++) { initSocketOK = true; try { // // It is mandatory for MSE to support initiation of bonding and // encryption. mServerSocket = mAdapter.listenUsingRfcommWithServiceRecord ("SIM Access", BluetoothUuid.SAP.getUuid()); // // TODO: Consider reusing the mServerSocket - it is intended // to be reused for multiple connections. // mServerSocket = mAdapter. listenUsingRfcommOn(BluetoothAdapter.SOCKET_CHANNEL_AUTO_STATIC_NO_SDP); int channel = mServerSocket.getChannel(); if (mSdpHandle >= 0) { SdpManager.getDefaultManager().removeSdpRecord(mSdpHandle); if (VERBOSE) Log.d(TAG, "Removing SDP record"); } mSdpHandle = SdpManager.getDefaultManager().createSapsRecord(SDP_SAP_SERVICE_NAME, mServerSocket.getChannel(), SDP_SAP_VERSION); } catch (IOException e) { Log.e(TAG, "Error create RfcommServerSocket ", e); Loading
android/app/src/com/android/bluetooth/sdp/SdpManager.java +51 −0 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ import android.bluetooth.SdpMasRecord; import android.bluetooth.SdpMnsRecord; import android.bluetooth.SdpOppOpsRecord; import android.bluetooth.SdpPseRecord; import android.bluetooth.SdpSapsRecord; import android.bluetooth.SdpRecord; import android.content.Intent; import android.os.Handler; Loading Loading @@ -107,6 +108,9 @@ public class SdpManager { private native int sdpCreateOppOpsRecordNative(String serviceName, int rfcommChannel, int l2capPsm, int version, byte[] formats_list); private native int sdpCreateSapsRecordNative(String serviceName, int rfcommChannel, int version); private native boolean sdpRemoveSdpRecordNative(int record_id); Loading Loading @@ -364,6 +368,31 @@ public class SdpManager { } } void sdpSapsRecordFoundCallback(int status, byte[] address, byte[] uuid, int rfcommCannelNumber, int profileVersion, String serviceName, boolean moreResults) { synchronized(mTrackerLock) { SdpSearchInstance inst = sSdpSearchTracker.getSearchInstance(address, uuid); SdpSapsRecord sdpRecord = null; if (inst == null) { Log.e(TAG, "sdpSapsRecordFoundCallback: Search instance is NULL"); return; } inst.setStatus(status); if (status == AbstractionLayer.BT_STATUS_SUCCESS) { sdpRecord = new SdpSapsRecord(rfcommCannelNumber, profileVersion, serviceName); } if (D) Log.d(TAG, "UUID: " + Arrays.toString(uuid)); if (D) Log.d(TAG, "UUID in parcel: " + ((Utils.byteArrayToUuid(uuid))[0]).toString()); sendSdpIntent(inst, sdpRecord, moreResults); } } /* TODO: Test or remove! */ void sdpRecordFoundCallback(int status, byte[] address, byte[] uuid, int size_record, byte[] record) { Loading Loading @@ -591,6 +620,28 @@ public class SdpManager { l2capPsm, version, formatsList); } /** * Create a server side Sim Access Profile Service Record. * Create the record once, and reuse it for all connections. * If changes to a record is needed remove the old record using {@link removeSdpRecord} * and then create a new one. * @param serviceName The textual name of the service * @param rfcommChannel The RFCOMM channel that clients can connect to * (obtain from BluetoothServerSocket) * @param version The Profile version number (As specified in the Bluetooth * SAP specification) * @return a handle to the record created. The record can be removed again * using {@link removeSdpRecord}(). The record is not linked to the * creation/destruction of BluetoothSockets, hence SDP record cleanup * is a separate process. */ public int createSapsRecord(String serviceName, int rfcommChannel, int version) { if (sNativeAvailable == false) { throw new RuntimeException(TAG + " sNativeAvailable == false - native not initialized"); } return sdpCreateSapsRecordNative(serviceName, rfcommChannel, version); } /** * Remove a SDP record. * When Bluetooth is disabled all records will be deleted, hence there Loading
android/app/tests/src/com/android/bluetooth/tests/SapServerTest.java +56 −31 Original line number Diff line number Diff line Loading @@ -30,8 +30,9 @@ import com.android.bluetooth.sap.SapServer; public class SapServerTest extends AndroidTestCase { protected static String TAG = "SapServerTest"; protected static final boolean D = true; private static final boolean rilTestModeEnabled = false; /* Set the RIL driver in test mode, where request stubs are used in stead of forwarding to the modem/sim */ // Set the RIL driver in test mode, where request stubs are used instead // of forwarding to the Modem/SIM. private static final boolean rilTestModeEnabled = false; private Context mContext = null; public SapServerTest() { Loading Loading @@ -176,16 +177,20 @@ public class SapServerTest extends AndroidTestCase { } sequencer.addStep(apdu7816Req, apdu7816Resp); SapMessage transferCardReaderStatusReq = new SapMessage(SapMessage.ID_TRANSFER_CARD_READER_STATUS_REQ); SapMessage transferCardReaderStatusReq = new SapMessage(SapMessage.ID_TRANSFER_CARD_READER_STATUS_REQ); SapMessage transferCardReaderStatusResp = new SapMessage(SapMessage.ID_TRANSFER_CARD_READER_STATUS_RESP); SapMessage transferCardReaderStatusResp = new SapMessage(SapMessage.ID_TRANSFER_CARD_READER_STATUS_RESP); transferCardReaderStatusResp.setResultCode(SapMessage.RESULT_OK); sequencer.addStep(transferCardReaderStatusReq, transferCardReaderStatusResp); SapMessage setTransportProtocolReq = new SapMessage(SapMessage.ID_SET_TRANSPORT_PROTOCOL_REQ); SapMessage setTransportProtocolReq = new SapMessage(SapMessage.ID_SET_TRANSPORT_PROTOCOL_REQ); setTransportProtocolReq.setTransportProtocol(0x01); // T=1 SapMessage setTransportProtocolResp = new SapMessage(SapMessage.ID_SET_TRANSPORT_PROTOCOL_RESP); SapMessage setTransportProtocolResp = new SapMessage(SapMessage.ID_SET_TRANSPORT_PROTOCOL_RESP); setTransportProtocolResp.setResultCode(SapMessage.RESULT_OK); sequencer.addStep(setTransportProtocolReq, setTransportProtocolResp); Loading Loading @@ -222,8 +227,12 @@ public class SapServerTest extends AndroidTestCase { SapMessage apduReq = new SapMessage(SapMessage.ID_TRANSFER_APDU_REQ); apduReq.setApdu(dummyBytes); SapMessage apduResp = null; /* expect no response as we send a SIM_RESET before the write APDU completes TODO: Consider adding a real response, and add an optional flag. */ // // Expect no response as we send a SIM_RESET before the write APDU // completes. // TODO: Consider adding a real response, and add an optional flag. // SapMessage apduResp = null; index = sequencer.addStep(apduReq, apduResp); SapMessage resetReq = new SapMessage(SapMessage.ID_RESET_SIM_REQ); Loading @@ -249,7 +258,8 @@ public class SapServerTest extends AndroidTestCase { */ public void testSapServerTimeouts() { Intent sapDisconnectIntent = new Intent(SapServer.SAP_DISCONNECT_ACTION); sapDisconnectIntent.putExtra(SapServer.SAP_DISCONNECT_TYPE_EXTRA, SapMessage.DISC_IMMEDIATE); sapDisconnectIntent.putExtra( SapServer.SAP_DISCONNECT_TYPE_EXTRA, SapMessage.DISC_IMMEDIATE); mContext = this.getContext(); try { Loading Loading @@ -298,7 +308,8 @@ public class SapServerTest extends AndroidTestCase { public class SeqStep { public ArrayList<SapMessage> requests = null; public ArrayList<SapMessage> responses = null; public int index = 0; /* requests with same index are executed in parallel (without waiting for a response) */ public int index = 0; // Requests with same index are executed in // parallel without waiting for a response public SeqStep(SapMessage request, SapMessage response) { requests = new ArrayList<SapMessage>(); responses = new ArrayList<SapMessage>(); Loading Loading @@ -328,7 +339,8 @@ public class SapServerTest extends AndroidTestCase { public SapSequencer() throws IOException { /* Setup the looper thread to handle messages */ handlerThread = new HandlerThread("SapTestTimeoutHandler", android.os.Process.THREAD_PRIORITY_BACKGROUND); handlerThread = new HandlerThread("SapTestTimeoutHandler", android.os.Process.THREAD_PRIORITY_BACKGROUND); handlerThread.start(); Looper testLooper = handlerThread.getLooper(); messageHandler = new Handler(testLooper, this); Loading @@ -339,7 +351,8 @@ public class SapServerTest extends AndroidTestCase { /* Create a SapServer. Fake the BtSocket using piped input/output streams*/ inStream = new PipedInputStream(8092); outStream = new PipedOutputStream(); sapServer = new SapServer(null, mContext, new PipedInputStream(outStream, 8092), new PipedOutputStream(inStream)); sapServer = new SapServer(null, mContext, new PipedInputStream(outStream, 8092), new PipedOutputStream(inStream)); sapServer.start(); } Loading @@ -364,15 +377,16 @@ public class SapServerTest extends AndroidTestCase { * @param response The response to EXPECT from the SAP server * @return The created step index, which can be used when adding events or actions. */ public int addStep(SapMessage request, SapMessage response) { // TODO: should we add a step trigger? (in stead of just executing in sequence) public int addStep(SapMessage request, SapMessage response) { // TODO: should we add a step trigger? (in stead of just executing in sequence) SeqStep newStep = new SeqStep(request, response); sequence.add(newStep); return sequence.indexOf(newStep); } /** * Add a sub-step to a sequencer step. All requests added to the same index will be send to the * SapServer in the order added before listening for the response. * Add a sub-step to a sequencer step. All requests added to the same index will be send to * the SapServer in the order added before listening for the response. * The response order is not validated - hence for each response received the entire list of * responses in the step will be searched for a match. * @param index the index returned from addStep() to which the sub-step is to be added. Loading @@ -399,8 +413,9 @@ public class SapServerTest extends AndroidTestCase { if(step.requests != null) { for(SapMessage request : step.requests) { if(request != null) { Log.i(TAG, "Writing request: " + SapMessage.getMsgTypeName(request.getMsgType())); writeSapMessage(request, false); /* write the message without flushing */ Log.i(TAG, "Writing request: " + SapMessage.getMsgTypeName(request.getMsgType())); writeSapMessage(request, false); // write the message without flushing } } writeSapMessage(null, true); /* flush the pipe */ Loading @@ -414,11 +429,13 @@ public class SapServerTest extends AndroidTestCase { while(!done) { for(SapMessage response : step.responses) { if(response != null) Log.i(TAG, "Waiting for the response: " + SapMessage.getMsgTypeName(response.getMsgType())); Log.i(TAG, "Waiting for the response: " + SapMessage.getMsgTypeName(response.getMsgType())); } inMsg = readSapMessage(); if(inMsg != null) Log.i(TAG, "Read message: " + SapMessage.getMsgTypeName(inMsg.getMsgType())); Log.i(TAG, "Read message: " + SapMessage.getMsgTypeName(inMsg.getMsgType())); else assertTrue("Failed to read message.", false); Loading Loading @@ -465,7 +482,8 @@ public class SapServerTest extends AndroidTestCase { */ private boolean compareSapMessages(SapMessage received, SapMessage expected) { boolean retVal = true; if(expected.getCardReaderStatus() != -1 && received.getCardReaderStatus() != expected.getCardReaderStatus()) { if(expected.getCardReaderStatus() != -1 && received.getCardReaderStatus() != expected.getCardReaderStatus()) { Log.i(TAG, "received.getCardReaderStatus() != expected.getCardReaderStatus() " + received.getCardReaderStatus() + " != " + expected.getCardReaderStatus()); retVal = false; Loading @@ -477,7 +495,8 @@ public class SapServerTest extends AndroidTestCase { } if(received.getDisconnectionType() != expected.getDisconnectionType()) { Log.i(TAG, "received.getDisconnectionType() != expected.getDisconnectionType() " + received.getDisconnectionType() +" != " + expected.getDisconnectionType()); + received.getDisconnectionType() + " != " + expected.getDisconnectionType()); retVal = false; } if(received.getMaxMsgSize() != expected.getMaxMsgSize()) { Loading @@ -502,27 +521,33 @@ public class SapServerTest extends AndroidTestCase { } if(received.getTransportProtocol() != expected.getTransportProtocol()) { Log.i(TAG, "received.getTransportProtocol() != expected.getTransportProtocol() " + received.getTransportProtocol() +" != " + expected.getTransportProtocol()); + received.getTransportProtocol() + " != " + expected.getTransportProtocol()); retVal = false; } if(!Arrays.equals(received.getApdu(), expected.getApdu())) { Log.i(TAG, "received.getApdu() != expected.getApdu() " + Arrays.toString(received.getApdu()) +" != " + Arrays.toString(expected.getApdu())); + Arrays.toString(received.getApdu()) + " != " + Arrays.toString(expected.getApdu())); retVal = false; } if(!Arrays.equals(received.getApdu7816(), expected.getApdu7816())) { Log.i(TAG, "received.getApdu7816() != expected.getApdu7816() " + Arrays.toString(received.getApdu7816()) +" != " + Arrays.toString(expected.getApdu7816())); + Arrays.toString(received.getApdu7816()) + " != " + Arrays.toString(expected.getApdu7816())); retVal = false; } if(expected.getApduResp() != null && !Arrays.equals(received.getApduResp(), expected.getApduResp())) { if(expected.getApduResp() != null && !Arrays.equals(received.getApduResp(), expected.getApduResp())) { Log.i(TAG, "received.getApduResp() != expected.getApduResp() " + Arrays.toString(received.getApduResp()) +" != " + Arrays.toString(expected.getApduResp())); + Arrays.toString(received.getApduResp()) + " != " + Arrays.toString(expected.getApduResp())); retVal = false; } if(expected.getAtr() != null && !Arrays.equals(received.getAtr(), expected.getAtr())) { Log.i(TAG, "received.getAtr() != expected.getAtr() " + Arrays.toString(received.getAtr()) +" != " + Arrays.toString(expected.getAtr())); + Arrays.toString(received.getAtr()) + " != " + Arrays.toString(expected.getAtr())); retVal = false; } return retVal; Loading
android/app/tests/src/com/android/bluetooth/tests/SdpManagerTest.java +8 −1 Original line number Diff line number Diff line Loading @@ -110,6 +110,12 @@ public class SdpManagerTest extends AndroidTestCase { Log.i(TAG, " Added record_handle=" + handles[record_id]); assertTrue(handles[record_id]>=0); if(record_id == count) break; handles[++record_id] = mManager.createSapsRecord(SDP_SERVER_NAME, record_id, SDP_VERSION); Log.i(TAG, " Added record_handle=" + handles[record_id]); assertTrue(handles[record_id]>=0); if (record_id == count) break; } } Loading Loading @@ -159,7 +165,8 @@ public class SdpManagerTest extends AndroidTestCase { final String[] uuids = {BluetoothUuid.MAS.toString(), BluetoothUuid.MNS.toString(), BluetoothUuid.PBAP_PSE.toString(), BluetoothUuid.ObexObjectPush.toString()}; BluetoothUuid.ObexObjectPush.toString(), BluetoothUuid.SAP.toString()}; final String uuids_str; final StringBuilder sb = new StringBuilder(uuids.length*2-1); for(String str : uuids) { Loading