1 /* 2 * Copyright (C) 2015 Samsung System LSI 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 package com.android.bluetooth.sdp; 16 17 import android.bluetooth.BluetoothDevice; 18 import android.bluetooth.SdpMasRecord; 19 import android.bluetooth.SdpMnsRecord; 20 import android.bluetooth.SdpOppOpsRecord; 21 import android.bluetooth.SdpPseRecord; 22 import android.bluetooth.SdpSapsRecord; 23 import android.bluetooth.SdpRecord; 24 import android.content.Intent; 25 import android.os.Handler; 26 import android.os.Message; 27 import android.os.ParcelUuid; 28 import android.os.Parcelable; 29 import android.util.Log; 30 31 import com.android.bluetooth.Utils; 32 import com.android.bluetooth.btservice.AbstractionLayer; 33 import com.android.bluetooth.btservice.AdapterService; 34 35 import java.util.ArrayList; 36 import java.util.Arrays; 37 38 public class SdpManager { 39 40 private static final boolean D = true; 41 private static final boolean V = false; 42 private static final String TAG="SdpManager"; 43 44 // TODO: When changing PBAP to use this new API. 45 // Move the defines to the profile (PBAP already have the feature bits) 46 /* PBAP repositories */ 47 public static final byte PBAP_REPO_LOCAL = 0x01<<0; 48 public static final byte PBAP_REPO_SIM = 0x01<<1; 49 public static final byte PBAP_REPO_SPEED_DAIL = 0x01<<2; 50 public static final byte PBAP_REPO_FAVORITES = 0x01<<3; 51 52 // TODO: When changing OPP to use this new API. 53 // Move the defines to the profile 54 /* Object Push formats */ 55 public static final byte OPP_FORMAT_VCARD21 = 0x01; 56 public static final byte OPP_FORMAT_VCARD30 = 0x02; 57 public static final byte OPP_FORMAT_VCAL10 = 0x03; 58 public static final byte OPP_FORMAT_ICAL20 = 0x04; 59 public static final byte OPP_FORMAT_VNOTE = 0x05; 60 public static final byte OPP_FORMAT_VMESSAGE = 0x06; 61 public static final byte OPP_FORMAT_ANY_TYPE_OF_OBJ = (byte)0xFF; 62 63 public static final byte[] OPP_FORMAT_ALL= { 64 OPP_FORMAT_VCARD21, 65 OPP_FORMAT_VCARD30, 66 OPP_FORMAT_VCAL10, 67 OPP_FORMAT_ICAL20, 68 OPP_FORMAT_VNOTE, 69 OPP_FORMAT_VMESSAGE, 70 OPP_FORMAT_ANY_TYPE_OF_OBJ}; 71 72 /* Variables to keep track of ongoing and queued search requests. 73 * mTrackerLock must be held, when using/changing sSdpSearchTracker 74 * and mSearchInProgress. */ 75 static SdpSearchTracker sSdpSearchTracker; 76 static boolean mSearchInProgress = false; 77 static Object mTrackerLock = new Object(); 78 79 /* The timeout to wait for reply from native. Should never fire. */ 80 private static final int SDP_INTENT_DELAY = 6000; 81 private static final int MESSAGE_SDP_INTENT = 2; 82 83 // We need a reference to the adapter service, to be able to send intents 84 private static AdapterService sAdapterService; 85 private static boolean sNativeAvailable; 86 87 // This object is a singleton 88 private static SdpManager sSdpManager = null; 89 90 static { 91 classInitNative(); 92 } 93 94 private native static void classInitNative(); 95 private native void initializeNative(); 96 private native void cleanupNative(); 97 private native boolean sdpSearchNative(byte[] address, byte[] uuid); 98 99 private native int sdpCreateMapMasRecordNative(String serviceName, int masId, 100 int rfcommChannel, int l2capPsm, int version, int msgTypes, int features); 101 102 private native int sdpCreateMapMnsRecordNative(String serviceName, 103 int rfcommChannel, int l2capPsm, int version, int features); 104 105 private native int sdpCreatePbapPseRecordNative(String serviceName, int rfcommChannel, 106 int l2capPsm, int version, int repositories, int features); 107 108 private native int sdpCreateOppOpsRecordNative(String serviceName, 109 int rfcommChannel, int l2capPsm, int version, byte[] formats_list); 110 111 private native int sdpCreateSapsRecordNative(String serviceName, int rfcommChannel, 112 int version); 113 114 private native boolean sdpRemoveSdpRecordNative(int record_id); 115 116 117 /* Inner class used for wrapping sdp search instance data */ 118 private class SdpSearchInstance { 119 private final BluetoothDevice mDevice; 120 private final ParcelUuid mUuid; 121 private int mStatus = 0; 122 private boolean mSearching; 123 /* TODO: If we change the API to use another mechanism than intents for 124 * delivering the results, this would be the place to keep a list 125 * of the objects to deliver the results to. */ 126 public SdpSearchInstance(int status, BluetoothDevice device, ParcelUuid uuid){ 127 this.mDevice = device; 128 this.mUuid = uuid; 129 this.mStatus = status; 130 mSearching = true; 131 } 132 public BluetoothDevice getDevice() { 133 return mDevice; 134 } 135 public ParcelUuid getUuid() { 136 return mUuid; 137 } 138 public int getStatus(){ 139 return mStatus; 140 } 141 142 public void setStatus(int status) { 143 this.mStatus = status; 144 } 145 146 public void startSearch() { 147 mSearching = true; 148 Message message = mHandler.obtainMessage(MESSAGE_SDP_INTENT, this); 149 mHandler.sendMessageDelayed(message, SDP_INTENT_DELAY); 150 } 151 152 public void stopSearch() { 153 if(mSearching) { 154 mHandler.removeMessages(MESSAGE_SDP_INTENT, this); 155 } 156 mSearching = false; 157 } 158 public boolean isSearching() { 159 return mSearching; 160 } 161 } 162 163 164 /* We wrap the ArrayList class to decorate with functionality to 165 * find an instance based on UUID AND device address. 166 * As we use a mix of byte[] and object instances, this is more 167 * efficient than implementing comparable. */ 168 class SdpSearchTracker { 169 private final ArrayList<SdpSearchInstance> list = new ArrayList<SdpSearchInstance>(); 170 171 void clear() { 172 list.clear(); 173 } 174 175 boolean add(SdpSearchInstance inst){ 176 return list.add(inst); 177 } 178 179 boolean remove(SdpSearchInstance inst) { 180 return list.remove(inst); 181 } 182 183 SdpSearchInstance getNext() { 184 if(list.size() > 0) { 185 return list.get(0); 186 } 187 return null; 188 } 189 190 SdpSearchInstance getSearchInstance(byte[] address, byte[] uuidBytes) { 191 String addressString = Utils.getAddressStringFromByte(address); 192 ParcelUuid uuid = Utils.byteArrayToUuid(uuidBytes)[0]; 193 for (SdpSearchInstance inst : list) { 194 if (inst.getDevice().getAddress().equals(addressString) 195 && inst.getUuid().equals(uuid)) { 196 return inst; 197 } 198 } 199 return null; 200 } 201 202 boolean isSearching(BluetoothDevice device, ParcelUuid uuid) { 203 String addressString = device.getAddress(); 204 for (SdpSearchInstance inst : list) { 205 if (inst.getDevice().getAddress().equals(addressString) 206 && inst.getUuid().equals(uuid)) { 207 return inst.isSearching(); 208 } 209 } 210 return false; 211 } 212 } 213 214 215 private SdpManager(AdapterService adapterService) { 216 sSdpSearchTracker = new SdpSearchTracker(); 217 218 /* This is only needed until intents are no longer used */ 219 sAdapterService = adapterService; 220 initializeNative(); 221 sNativeAvailable=true; 222 } 223 224 225 public static SdpManager init(AdapterService adapterService) { 226 sSdpManager = new SdpManager(adapterService); 227 return sSdpManager; 228 } 229 230 public static SdpManager getDefaultManager() { 231 return sSdpManager; 232 } 233 234 public void cleanup() { 235 if (sSdpSearchTracker !=null) { 236 synchronized(mTrackerLock) { 237 sSdpSearchTracker.clear(); 238 } 239 } 240 241 if (sNativeAvailable) { 242 cleanupNative(); 243 sNativeAvailable=false; 244 } 245 sSdpManager = null; 246 } 247 248 249 void sdpMasRecordFoundCallback(int status, byte[] address, byte[] uuid, 250 int masInstanceId, 251 int l2capPsm, 252 int rfcommCannelNumber, 253 int profileVersion, 254 int supportedFeatures, 255 int supportedMessageTypes, 256 String serviceName, 257 boolean moreResults) { 258 259 synchronized(mTrackerLock) { 260 SdpSearchInstance inst = sSdpSearchTracker.getSearchInstance(address, uuid); 261 SdpMasRecord sdpRecord = null; 262 if (inst == null) { 263 Log.e(TAG, "sdpRecordFoundCallback: Search instance is NULL"); 264 return; 265 } 266 inst.setStatus(status); 267 if(status == AbstractionLayer.BT_STATUS_SUCCESS) { 268 sdpRecord = new SdpMasRecord(masInstanceId, 269 l2capPsm, 270 rfcommCannelNumber, 271 profileVersion, 272 supportedFeatures, 273 supportedMessageTypes, 274 serviceName); 275 } 276 if(D) Log.d(TAG, "UUID: " + Arrays.toString(uuid)); 277 if(D) Log.d(TAG, "UUID in parcel: " + ((Utils.byteArrayToUuid(uuid))[0]).toString()); 278 sendSdpIntent(inst, sdpRecord, moreResults); 279 } 280 } 281 282 void sdpMnsRecordFoundCallback(int status, byte[] address, byte[] uuid, 283 int l2capPsm, 284 int rfcommCannelNumber, 285 int profileVersion, 286 int supportedFeatures, 287 String serviceName, 288 boolean moreResults) { 289 synchronized(mTrackerLock) { 290 291 SdpSearchInstance inst = sSdpSearchTracker.getSearchInstance(address, uuid); 292 SdpMnsRecord sdpRecord = null; 293 if (inst == null) { 294 Log.e(TAG, "sdpRecordFoundCallback: Search instance is NULL"); 295 return; 296 } 297 inst.setStatus(status); 298 if(status == AbstractionLayer.BT_STATUS_SUCCESS) { 299 sdpRecord = new SdpMnsRecord(l2capPsm, 300 rfcommCannelNumber, 301 profileVersion, 302 supportedFeatures, 303 serviceName); 304 } 305 if(D) Log.d(TAG, "UUID: " + Arrays.toString(uuid)); 306 if(D) Log.d(TAG, "UUID in parcel: " + ((Utils.byteArrayToUuid(uuid))[0]).toString()); 307 sendSdpIntent(inst, sdpRecord, moreResults); 308 } 309 } 310 311 void sdpPseRecordFoundCallback(int status, byte[] address, byte[] uuid, 312 int l2capPsm, 313 int rfcommCannelNumber, 314 int profileVersion, 315 int supportedFeatures, 316 int supportedRepositories, 317 String serviceName, 318 boolean moreResults) { 319 synchronized(mTrackerLock) { 320 SdpSearchInstance inst = sSdpSearchTracker.getSearchInstance(address, uuid); 321 SdpPseRecord sdpRecord = null; 322 if (inst == null) { 323 Log.e(TAG, "sdpRecordFoundCallback: Search instance is NULL"); 324 return; 325 } 326 inst.setStatus(status); 327 if(status == AbstractionLayer.BT_STATUS_SUCCESS) { 328 sdpRecord = new SdpPseRecord(l2capPsm, 329 rfcommCannelNumber, 330 profileVersion, 331 supportedFeatures, 332 supportedRepositories, 333 serviceName); 334 } 335 if(D) Log.d(TAG, "UUID: " + Arrays.toString(uuid)); 336 if(D) Log.d(TAG, "UUID in parcel: " + ((Utils.byteArrayToUuid(uuid))[0]).toString()); 337 sendSdpIntent(inst, sdpRecord, moreResults); 338 } 339 } 340 341 void sdpOppOpsRecordFoundCallback(int status, byte[] address, byte[] uuid, 342 int l2capPsm, 343 int rfcommCannelNumber, 344 int profileVersion, 345 String serviceName, 346 byte[] formatsList, 347 boolean moreResults) { 348 349 synchronized(mTrackerLock) { 350 SdpSearchInstance inst = sSdpSearchTracker.getSearchInstance(address, uuid); 351 SdpOppOpsRecord sdpRecord = null; 352 353 if (inst == null) { 354 Log.e(TAG, "sdpOppOpsRecordFoundCallback: Search instance is NULL"); 355 return; 356 } 357 inst.setStatus(status); 358 if(status == AbstractionLayer.BT_STATUS_SUCCESS) { 359 sdpRecord = new SdpOppOpsRecord(serviceName, 360 rfcommCannelNumber, 361 l2capPsm, 362 profileVersion, 363 formatsList); 364 } 365 if(D) Log.d(TAG, "UUID: " + Arrays.toString(uuid)); 366 if(D) Log.d(TAG, "UUID in parcel: " + ((Utils.byteArrayToUuid(uuid))[0]).toString()); 367 sendSdpIntent(inst, sdpRecord, moreResults); 368 } 369 } 370 371 void sdpSapsRecordFoundCallback(int status, byte[] address, byte[] uuid, 372 int rfcommCannelNumber, 373 int profileVersion, 374 String serviceName, 375 boolean moreResults) { 376 377 synchronized(mTrackerLock) { 378 SdpSearchInstance inst = sSdpSearchTracker.getSearchInstance(address, uuid); 379 SdpSapsRecord sdpRecord = null; 380 if (inst == null) { 381 Log.e(TAG, "sdpSapsRecordFoundCallback: Search instance is NULL"); 382 return; 383 } 384 inst.setStatus(status); 385 if (status == AbstractionLayer.BT_STATUS_SUCCESS) { 386 sdpRecord = new SdpSapsRecord(rfcommCannelNumber, 387 profileVersion, 388 serviceName); 389 } 390 if (D) Log.d(TAG, "UUID: " + Arrays.toString(uuid)); 391 if (D) Log.d(TAG, "UUID in parcel: " + ((Utils.byteArrayToUuid(uuid))[0]).toString()); 392 sendSdpIntent(inst, sdpRecord, moreResults); 393 } 394 } 395 396 /* TODO: Test or remove! */ 397 void sdpRecordFoundCallback(int status, byte[] address, byte[] uuid, 398 int size_record, byte[] record) { 399 synchronized(mTrackerLock) { 400 401 SdpSearchInstance inst = sSdpSearchTracker.getSearchInstance(address, uuid); 402 SdpRecord sdpRecord = null; 403 if (inst == null) { 404 Log.e(TAG, "sdpRecordFoundCallback: Search instance is NULL"); 405 return; 406 } 407 inst.setStatus(status); 408 if(status == AbstractionLayer.BT_STATUS_SUCCESS) { 409 if(D) Log.d(TAG, "sdpRecordFoundCallback: found a sdp record of size " 410 + size_record ); 411 if(D) Log.d(TAG, "Record:"+ Arrays.toString(record)); 412 sdpRecord = new SdpRecord(size_record, record); 413 } 414 if(D) Log.d(TAG, "UUID: " + Arrays.toString(uuid)); 415 if(D) Log.d(TAG, "UUID in parcel: " + ((Utils.byteArrayToUuid(uuid))[0]).toString()); 416 sendSdpIntent(inst, sdpRecord, false); 417 } 418 } 419 420 public void sdpSearch(BluetoothDevice device, ParcelUuid uuid) { 421 if (sNativeAvailable == false) { 422 Log.e(TAG, "Native not initialized!"); 423 return; 424 } 425 synchronized (mTrackerLock) { 426 if (sSdpSearchTracker.isSearching(device, uuid)) { 427 /* Search already in progress */ 428 return; 429 } 430 431 SdpSearchInstance inst = new SdpSearchInstance(0, device, uuid); 432 sSdpSearchTracker.add(inst); // Queue the request 433 434 startSearch(); // Start search if not busy 435 } 436 437 } 438 439 /* Caller must hold the mTrackerLock */ 440 private void startSearch() { 441 442 SdpSearchInstance inst = sSdpSearchTracker.getNext(); 443 444 if((inst != null) && (mSearchInProgress == false)) { 445 if(D) Log.d(TAG, "Starting search for UUID: "+ inst.getUuid()); 446 mSearchInProgress = true; 447 448 inst.startSearch(); // Trigger timeout message 449 450 sdpSearchNative(Utils.getBytesFromAddress(inst.getDevice().getAddress()), 451 Utils.uuidToByteArray(inst.getUuid())); 452 } // Else queue is empty. 453 else { 454 if(D) Log.d(TAG, "startSearch(): nextInst = " + inst + 455 " mSearchInProgress = " + mSearchInProgress 456 + " - search busy or queue empty."); 457 } 458 } 459 460 /* Caller must hold the mTrackerLock */ 461 private void sendSdpIntent(SdpSearchInstance inst, 462 Parcelable record, boolean moreResults) { 463 464 inst.stopSearch(); 465 466 Intent intent = new Intent(BluetoothDevice.ACTION_SDP_RECORD); 467 468 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, inst.getDevice()); 469 intent.putExtra(BluetoothDevice.EXTRA_SDP_SEARCH_STATUS, inst.getStatus()); 470 if (record != null) intent.putExtra(BluetoothDevice.EXTRA_SDP_RECORD, record); 471 intent.putExtra(BluetoothDevice.EXTRA_UUID, inst.getUuid()); 472 /* TODO: BLUETOOTH_ADMIN_PERM was private... change to callback interface. 473 * Keep in mind that the MAP client needs to use this as well, 474 * hence to make it call-backs, the MAP client profile needs to be 475 * part of the Bluetooth APK. */ 476 sAdapterService.sendBroadcast(intent, AdapterService.BLUETOOTH_ADMIN_PERM); 477 478 if(moreResults == false) { 479 //Remove the outstanding UUID request 480 sSdpSearchTracker.remove(inst); 481 mSearchInProgress = false; 482 startSearch(); 483 } 484 } 485 486 private final Handler mHandler = new Handler() { 487 @Override 488 public void handleMessage(Message msg) { 489 switch (msg.what) { 490 case MESSAGE_SDP_INTENT: 491 SdpSearchInstance msgObj = (SdpSearchInstance)msg.obj; 492 Log.w(TAG, "Search timedout for UUID " + msgObj.getUuid()); 493 synchronized (mTrackerLock) { 494 sendSdpIntent(msgObj, null, false); 495 } 496 break; 497 } 498 } 499 }; 500 501 /** 502 * Create a server side Message Access Profile Service Record. 503 * Create the record once, and reuse it for all connections. 504 * If changes to a record is needed remove the old record using {@link removeSdpRecord} 505 * and then create a new one. 506 * @param serviceName The textual name of the service 507 * @param masId The MAS ID to associate with this SDP record 508 * @param rfcommChannel The RFCOMM channel that clients can connect to 509 * (obtain from BluetoothServerSocket) 510 * @param l2capPsm The L2CAP PSM channel that clients can connect to 511 * (obtain from BluetoothServerSocket) 512 * Supply -1 to omit the L2CAP PSM from the record. 513 * @param version The Profile version number (As specified in the Bluetooth 514 * MAP specification) 515 * @param msgTypes The supported message types bit mask (As specified in 516 * the Bluetooth MAP specification) 517 * @param features The feature bit mask (As specified in the Bluetooth 518 * MAP specification) 519 * @return a handle to the record created. The record can be removed again 520 * using {@link removeSdpRecord}(). The record is not linked to the 521 * creation/destruction of BluetoothSockets, hence SDP record cleanup 522 * is a separate process. 523 */ 524 public int createMapMasRecord(String serviceName, int masId, 525 int rfcommChannel, int l2capPsm, int version, 526 int msgTypes, int features) { 527 if(sNativeAvailable == false) { 528 throw new RuntimeException(TAG + " sNativeAvailable == false - native not initialized"); 529 } 530 return sdpCreateMapMasRecordNative(serviceName, masId, rfcommChannel, 531 l2capPsm, version, msgTypes, features); 532 } 533 534 /** 535 * Create a client side Message Access Profile Service Record. 536 * Create the record once, and reuse it for all connections. 537 * If changes to a record is needed remove the old record using {@link removeSdpRecord} 538 * and then create a new one. 539 * @param serviceName The textual name of the service 540 * @param rfcommChannel The RFCOMM channel that clients can connect to 541 * (obtain from BluetoothServerSocket) 542 * @param l2capPsm The L2CAP PSM channel that clients can connect to 543 * (obtain from BluetoothServerSocket) 544 * Supply -1 to omit the L2CAP PSM from the record. 545 * @param version The Profile version number (As specified in the Bluetooth 546 * MAP specification) 547 * @param features The feature bit mask (As specified in the Bluetooth 548 * MAP specification) 549 * @return a handle to the record created. The record can be removed again 550 * using {@link removeSdpRecord}(). The record is not linked to the 551 * creation/destruction of BluetoothSockets, hence SDP record cleanup 552 * is a separate process. 553 */ 554 public int createMapMnsRecord(String serviceName, int rfcommChannel, 555 int l2capPsm, int version, int features) { 556 if(sNativeAvailable == false) { 557 throw new RuntimeException(TAG + " sNativeAvailable == false - native not initialized"); 558 } 559 return sdpCreateMapMnsRecordNative(serviceName, rfcommChannel, 560 l2capPsm, version, features); 561 } 562 563 /** 564 * Create a Server side Phone Book Access Profile Service Record. 565 * Create the record once, and reuse it for all connections. 566 * If changes to a record is needed remove the old record using {@link removeSdpRecord} 567 * and then create a new one. 568 * @param serviceName The textual name of the service 569 * @param rfcommChannel The RFCOMM channel that clients can connect to 570 * (obtain from BluetoothServerSocket) 571 * @param l2capPsm The L2CAP PSM channel that clients can connect to 572 * (obtain from BluetoothServerSocket) 573 * Supply -1 to omit the L2CAP PSM from the record. 574 * @param version The Profile version number (As specified in the Bluetooth 575 * PBAP specification) 576 * @param repositories The supported repositories bit mask (As specified in 577 * the Bluetooth PBAP specification) 578 * @param features The feature bit mask (As specified in the Bluetooth 579 * PBAP specification) 580 * @return a handle to the record created. The record can be removed again 581 * using {@link removeSdpRecord}(). The record is not linked to the 582 * creation/destruction of BluetoothSockets, hence SDP record cleanup 583 * is a separate process. 584 */ 585 public int createPbapPseRecord(String serviceName, int rfcommChannel, int l2capPsm, 586 int version, int repositories, int features) { 587 if(sNativeAvailable == false) { 588 throw new RuntimeException(TAG + " sNativeAvailable == false - native not initialized"); 589 } 590 return sdpCreatePbapPseRecordNative(serviceName, rfcommChannel, 591 l2capPsm, version, repositories, features); 592 } 593 594 /** 595 * Create a Server side Object Push Profile Service Record. 596 * Create the record once, and reuse it for all connections. 597 * If changes to a record is needed remove the old record using {@link removeSdpRecord} 598 * and then create a new one. 599 * @param serviceName The textual name of the service 600 * @param rfcommChannel The RFCOMM channel that clients can connect to 601 * (obtain from BluetoothServerSocket) 602 * @param l2capPsm The L2CAP PSM channel that clients can connect to 603 * (obtain from BluetoothServerSocket) 604 * Supply -1 to omit the L2CAP PSM from the record. 605 * @param version The Profile version number (As specified in the Bluetooth 606 * OPP specification) 607 * @param formatsList A list of the supported formats (As specified in 608 * the Bluetooth OPP specification) 609 * @return a handle to the record created. The record can be removed again 610 * using {@link removeSdpRecord}(). The record is not linked to the 611 * creation/destruction of BluetoothSockets, hence SDP record cleanup 612 * is a separate process. 613 */ 614 public int createOppOpsRecord(String serviceName, int rfcommChannel, int l2capPsm, 615 int version, byte[] formatsList) { 616 if(sNativeAvailable == false) { 617 throw new RuntimeException(TAG + " sNativeAvailable == false - native not initialized"); 618 } 619 return sdpCreateOppOpsRecordNative(serviceName, rfcommChannel, 620 l2capPsm, version, formatsList); 621 } 622 623 /** 624 * Create a server side Sim Access Profile Service Record. 625 * Create the record once, and reuse it for all connections. 626 * If changes to a record is needed remove the old record using {@link removeSdpRecord} 627 * and then create a new one. 628 * @param serviceName The textual name of the service 629 * @param rfcommChannel The RFCOMM channel that clients can connect to 630 * (obtain from BluetoothServerSocket) 631 * @param version The Profile version number (As specified in the Bluetooth 632 * SAP specification) 633 * @return a handle to the record created. The record can be removed again 634 * using {@link removeSdpRecord}(). The record is not linked to the 635 * creation/destruction of BluetoothSockets, hence SDP record cleanup 636 * is a separate process. 637 */ 638 public int createSapsRecord(String serviceName, int rfcommChannel, int version) { 639 if (sNativeAvailable == false) { 640 throw new RuntimeException(TAG + " sNativeAvailable == false - native not initialized"); 641 } 642 return sdpCreateSapsRecordNative(serviceName, rfcommChannel, version); 643 } 644 645 /** 646 * Remove a SDP record. 647 * When Bluetooth is disabled all records will be deleted, hence there 648 * is no need to call this function when bluetooth is disabled. 649 * @param recordId The Id returned by on of the createXxxXxxRecord() functions. 650 * @return TRUE if the record removal was initiated successfully. FALSE if the record 651 * handle is not known/have already been removed. 652 */ 653 public boolean removeSdpRecord(int recordId){ 654 if(sNativeAvailable == false) { 655 throw new RuntimeException(TAG + " sNativeAvailable == false - native not initialized"); 656 } 657 return sdpRemoveSdpRecordNative(recordId); 658 } 659 } 660