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