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