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 addressString = sAdapterService.getIdentityAddress(addressString); 187 ParcelUuid uuid = Utils.byteArrayToUuid(uuidBytes)[0]; 188 for (SdpSearchInstance inst : mList) { 189 String instAddressString = 190 sAdapterService.getIdentityAddress(inst.getDevice().getAddress()); 191 if (instAddressString.equals(addressString) && inst.getUuid().equals(uuid)) { 192 return inst; 193 } 194 } 195 return null; 196 } 197 isSearching(BluetoothDevice device, ParcelUuid uuid)198 boolean isSearching(BluetoothDevice device, ParcelUuid uuid) { 199 String addressString = sAdapterService.getIdentityAddress(device.getAddress()); 200 for (SdpSearchInstance inst : mList) { 201 String instAddressString = 202 sAdapterService.getIdentityAddress(inst.getDevice().getAddress()); 203 if (instAddressString.equals(addressString) && inst.getUuid().equals(uuid)) { 204 return inst.isSearching(); 205 } 206 } 207 return false; 208 } 209 } 210 211 SdpManager(AdapterService adapterService)212 private SdpManager(AdapterService adapterService) { 213 sSdpSearchTracker = new SdpSearchTracker(); 214 sAdapterService = adapterService; 215 initializeNative(); 216 sNativeAvailable = true; 217 } 218 219 init(AdapterService adapterService)220 public static SdpManager init(AdapterService adapterService) { 221 sSdpManager = new SdpManager(adapterService); 222 return sSdpManager; 223 } 224 getDefaultManager()225 public static SdpManager getDefaultManager() { 226 return sSdpManager; 227 } 228 cleanup()229 public void cleanup() { 230 if (sSdpSearchTracker != null) { 231 synchronized (TRACKER_LOCK) { 232 sSdpSearchTracker.clear(); 233 } 234 } 235 236 if (sNativeAvailable) { 237 cleanupNative(); 238 sNativeAvailable = false; 239 } 240 sSdpManager = null; 241 } 242 243 sdpMasRecordFoundCallback(int status, byte[] address, byte[] uuid, int masInstanceId, int l2capPsm, int rfcommCannelNumber, int profileVersion, int supportedFeatures, int supportedMessageTypes, String serviceName, boolean moreResults)244 void sdpMasRecordFoundCallback(int status, byte[] address, byte[] uuid, int masInstanceId, 245 int l2capPsm, int rfcommCannelNumber, int profileVersion, int supportedFeatures, 246 int supportedMessageTypes, String serviceName, boolean moreResults) { 247 248 synchronized (TRACKER_LOCK) { 249 SdpSearchInstance inst = sSdpSearchTracker.getSearchInstance(address, uuid); 250 SdpMasRecord sdpRecord = null; 251 if (inst == null) { 252 Log.e(TAG, "sdpRecordFoundCallback: Search instance is NULL"); 253 return; 254 } 255 inst.setStatus(status); 256 if (status == AbstractionLayer.BT_STATUS_SUCCESS) { 257 sdpRecord = new SdpMasRecord(masInstanceId, l2capPsm, rfcommCannelNumber, 258 profileVersion, supportedFeatures, supportedMessageTypes, serviceName); 259 } 260 if (D) { 261 Log.d(TAG, "UUID: " + Arrays.toString(uuid)); 262 } 263 if (D) { 264 Log.d(TAG, "UUID in parcel: " + ((Utils.byteArrayToUuid(uuid))[0]).toString()); 265 } 266 sendSdpIntent(inst, sdpRecord, moreResults); 267 } 268 } 269 sdpMnsRecordFoundCallback(int status, byte[] address, byte[] uuid, int l2capPsm, int rfcommCannelNumber, int profileVersion, int supportedFeatures, String serviceName, boolean moreResults)270 void sdpMnsRecordFoundCallback(int status, byte[] address, byte[] uuid, int l2capPsm, 271 int rfcommCannelNumber, int profileVersion, int supportedFeatures, String serviceName, 272 boolean moreResults) { 273 synchronized (TRACKER_LOCK) { 274 275 SdpSearchInstance inst = sSdpSearchTracker.getSearchInstance(address, uuid); 276 SdpMnsRecord sdpRecord = null; 277 if (inst == null) { 278 Log.e(TAG, "sdpRecordFoundCallback: Search instance is NULL"); 279 return; 280 } 281 inst.setStatus(status); 282 if (status == AbstractionLayer.BT_STATUS_SUCCESS) { 283 sdpRecord = new SdpMnsRecord(l2capPsm, rfcommCannelNumber, profileVersion, 284 supportedFeatures, serviceName); 285 } 286 if (D) { 287 Log.d(TAG, "UUID: " + Arrays.toString(uuid)); 288 } 289 if (D) { 290 Log.d(TAG, "UUID in parcel: " + ((Utils.byteArrayToUuid(uuid))[0]).toString()); 291 } 292 sendSdpIntent(inst, sdpRecord, moreResults); 293 } 294 } 295 sdpPseRecordFoundCallback(int status, byte[] address, byte[] uuid, int l2capPsm, int rfcommCannelNumber, int profileVersion, int supportedFeatures, int supportedRepositories, String serviceName, boolean moreResults)296 void sdpPseRecordFoundCallback(int status, byte[] address, byte[] uuid, int l2capPsm, 297 int rfcommCannelNumber, int profileVersion, int supportedFeatures, 298 int supportedRepositories, String serviceName, boolean moreResults) { 299 synchronized (TRACKER_LOCK) { 300 SdpSearchInstance inst = sSdpSearchTracker.getSearchInstance(address, uuid); 301 SdpPseRecord sdpRecord = null; 302 if (inst == null) { 303 Log.e(TAG, "sdpRecordFoundCallback: Search instance is NULL"); 304 return; 305 } 306 inst.setStatus(status); 307 if (status == AbstractionLayer.BT_STATUS_SUCCESS) { 308 sdpRecord = new SdpPseRecord(l2capPsm, rfcommCannelNumber, profileVersion, 309 supportedFeatures, supportedRepositories, serviceName); 310 } 311 if (D) { 312 Log.d(TAG, "UUID: " + Arrays.toString(uuid)); 313 } 314 if (D) { 315 Log.d(TAG, "UUID in parcel: " + ((Utils.byteArrayToUuid(uuid))[0]).toString()); 316 } 317 sendSdpIntent(inst, sdpRecord, moreResults); 318 } 319 } 320 sdpOppOpsRecordFoundCallback(int status, byte[] address, byte[] uuid, int l2capPsm, int rfcommCannelNumber, int profileVersion, String serviceName, byte[] formatsList, boolean moreResults)321 void sdpOppOpsRecordFoundCallback(int status, byte[] address, byte[] uuid, int l2capPsm, 322 int rfcommCannelNumber, int profileVersion, String serviceName, byte[] formatsList, 323 boolean moreResults) { 324 325 synchronized (TRACKER_LOCK) { 326 SdpSearchInstance inst = sSdpSearchTracker.getSearchInstance(address, uuid); 327 SdpOppOpsRecord sdpRecord = null; 328 329 if (inst == null) { 330 Log.e(TAG, "sdpOppOpsRecordFoundCallback: Search instance is NULL"); 331 return; 332 } 333 inst.setStatus(status); 334 if (status == AbstractionLayer.BT_STATUS_SUCCESS) { 335 sdpRecord = new SdpOppOpsRecord(serviceName, rfcommCannelNumber, l2capPsm, 336 profileVersion, formatsList); 337 } 338 if (D) { 339 Log.d(TAG, "UUID: " + Arrays.toString(uuid)); 340 } 341 if (D) { 342 Log.d(TAG, "UUID in parcel: " + ((Utils.byteArrayToUuid(uuid))[0]).toString()); 343 } 344 sendSdpIntent(inst, sdpRecord, moreResults); 345 } 346 } 347 sdpSapsRecordFoundCallback(int status, byte[] address, byte[] uuid, int rfcommCannelNumber, int profileVersion, String serviceName, boolean moreResults)348 void sdpSapsRecordFoundCallback(int status, byte[] address, byte[] uuid, int rfcommCannelNumber, 349 int profileVersion, String serviceName, boolean moreResults) { 350 351 synchronized (TRACKER_LOCK) { 352 SdpSearchInstance inst = sSdpSearchTracker.getSearchInstance(address, uuid); 353 SdpSapsRecord sdpRecord = null; 354 if (inst == null) { 355 Log.e(TAG, "sdpSapsRecordFoundCallback: Search instance is NULL"); 356 return; 357 } 358 inst.setStatus(status); 359 if (status == AbstractionLayer.BT_STATUS_SUCCESS) { 360 sdpRecord = new SdpSapsRecord(rfcommCannelNumber, profileVersion, serviceName); 361 } 362 if (D) { 363 Log.d(TAG, "UUID: " + Arrays.toString(uuid)); 364 } 365 if (D) { 366 Log.d(TAG, "UUID in parcel: " + ((Utils.byteArrayToUuid(uuid))[0]).toString()); 367 } 368 sendSdpIntent(inst, sdpRecord, moreResults); 369 } 370 } 371 sdpDipRecordFoundCallback(int status, byte[] address, byte[] uuid, int specificationId, int vendorId, int vendorIdSource, int productId, int version, boolean primaryRecord, boolean moreResults)372 void sdpDipRecordFoundCallback(int status, byte[] address, 373 byte[] uuid, int specificationId, 374 int vendorId, int vendorIdSource, 375 int productId, int version, 376 boolean primaryRecord, 377 boolean moreResults) { 378 synchronized(TRACKER_LOCK) { 379 SdpSearchInstance inst = sSdpSearchTracker.getSearchInstance(address, uuid); 380 SdpDipRecord sdpRecord = null; 381 if (inst == null) { 382 Log.e(TAG, "sdpDipRecordFoundCallback: Search instance is NULL"); 383 return; 384 } 385 inst.setStatus(status); 386 if (D) { 387 Log.d(TAG, "sdpDipRecordFoundCallback: status " + status); 388 } 389 if (status == AbstractionLayer.BT_STATUS_SUCCESS) { 390 sdpRecord = new SdpDipRecord(specificationId, 391 vendorId, vendorIdSource, 392 productId, version, 393 primaryRecord); 394 } 395 if (D) { 396 Log.d(TAG, "UUID: " + Arrays.toString(uuid)); 397 } 398 if (D) { 399 Log.d(TAG, "UUID in parcel: " + ((Utils.byteArrayToUuid(uuid))[0]).toString()); 400 } 401 sendSdpIntent(inst, sdpRecord, moreResults); 402 } 403 } 404 405 /* TODO: Test or remove! */ sdpRecordFoundCallback(int status, byte[] address, byte[] uuid, int sizeRecord, byte[] record)406 void sdpRecordFoundCallback(int status, byte[] address, byte[] uuid, int sizeRecord, 407 byte[] record) { 408 synchronized (TRACKER_LOCK) { 409 410 SdpSearchInstance inst = sSdpSearchTracker.getSearchInstance(address, uuid); 411 SdpRecord sdpRecord = null; 412 if (inst == null) { 413 Log.e(TAG, "sdpRecordFoundCallback: Search instance is NULL"); 414 return; 415 } 416 inst.setStatus(status); 417 if (status == AbstractionLayer.BT_STATUS_SUCCESS) { 418 if (D) { 419 Log.d(TAG, "sdpRecordFoundCallback: found a sdp record of size " + sizeRecord); 420 } 421 if (D) { 422 Log.d(TAG, "Record:" + Arrays.toString(record)); 423 } 424 sdpRecord = new SdpRecord(sizeRecord, record); 425 } 426 if (D) { 427 Log.d(TAG, "UUID: " + Arrays.toString(uuid)); 428 } 429 if (D) { 430 Log.d(TAG, "UUID in parcel: " + ((Utils.byteArrayToUuid(uuid))[0]).toString()); 431 } 432 sendSdpIntent(inst, sdpRecord, false); 433 } 434 } 435 sdpSearch(BluetoothDevice device, ParcelUuid uuid)436 public void sdpSearch(BluetoothDevice device, ParcelUuid uuid) { 437 if (!sNativeAvailable) { 438 Log.e(TAG, "Native not initialized!"); 439 return; 440 } 441 synchronized (TRACKER_LOCK) { 442 if (sSdpSearchTracker.isSearching(device, uuid)) { 443 /* Search already in progress */ 444 return; 445 } 446 447 SdpSearchInstance inst = new SdpSearchInstance(0, device, uuid); 448 sSdpSearchTracker.add(inst); // Queue the request 449 450 startSearch(); // Start search if not busy 451 } 452 453 } 454 455 /* Caller must hold the mTrackerLock */ startSearch()456 private void startSearch() { 457 458 SdpSearchInstance inst = sSdpSearchTracker.getNext(); 459 460 if ((inst != null) && (!sSearchInProgress)) { 461 if (D) { 462 Log.d(TAG, "Starting search for UUID: " + inst.getUuid()); 463 } 464 sSearchInProgress = true; 465 466 inst.startSearch(); // Trigger timeout message 467 468 sdpSearchNative(sAdapterService.getByteIdentityAddress(inst.getDevice()), 469 Utils.uuidToByteArray(inst.getUuid())); 470 } else { // Else queue is empty. 471 if (D) { 472 Log.d(TAG, "startSearch(): nextInst = " + inst + " mSearchInProgress = " 473 + sSearchInProgress + " - search busy or queue empty."); 474 } 475 } 476 } 477 478 /* Caller must hold the mTrackerLock */ sendSdpIntent(SdpSearchInstance inst, Parcelable record, boolean moreResults)479 private void sendSdpIntent(SdpSearchInstance inst, Parcelable record, boolean moreResults) { 480 481 inst.stopSearch(); 482 483 Intent intent = new Intent(BluetoothDevice.ACTION_SDP_RECORD); 484 485 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, inst.getDevice()); 486 intent.putExtra(BluetoothDevice.EXTRA_SDP_SEARCH_STATUS, inst.getStatus()); 487 if (record != null) { 488 intent.putExtra(BluetoothDevice.EXTRA_SDP_RECORD, record); 489 } 490 intent.putExtra(BluetoothDevice.EXTRA_UUID, inst.getUuid()); 491 /* TODO: BLUETOOTH_ADMIN_PERM was private... change to callback interface. 492 * Keep in mind that the MAP client needs to use this as well, 493 * hence to make it call-backs, the MAP client profile needs to be 494 * part of the Bluetooth APK. */ 495 Utils.sendBroadcast(sAdapterService, intent, BLUETOOTH_CONNECT, 496 Utils.getTempAllowlistBroadcastOptions()); 497 498 if (!moreResults) { 499 //Remove the outstanding UUID request 500 sSdpSearchTracker.remove(inst); 501 sSearchInProgress = false; 502 startSearch(); 503 } 504 } 505 506 private final Handler mHandler = new Handler() { 507 @Override 508 public void handleMessage(Message msg) { 509 switch (msg.what) { 510 case MESSAGE_SDP_INTENT: 511 SdpSearchInstance msgObj = (SdpSearchInstance) msg.obj; 512 Log.w(TAG, "Search timedout for UUID " + msgObj.getUuid()); 513 synchronized (TRACKER_LOCK) { 514 sendSdpIntent(msgObj, null, false); 515 } 516 break; 517 } 518 } 519 }; 520 521 /** 522 * Create a server side Message Access Profile Service Record. 523 * Create the record once, and reuse it for all connections. 524 * If changes to a record is needed remove the old record using {@link removeSdpRecord} 525 * and then create a new one. 526 * @param serviceName The textual name of the service 527 * @param masId The MAS ID to associate with this SDP record 528 * @param rfcommChannel The RFCOMM channel that clients can connect to 529 * (obtain from BluetoothServerSocket) 530 * @param l2capPsm The L2CAP PSM channel that clients can connect to 531 * (obtain from BluetoothServerSocket) 532 * Supply -1 to omit the L2CAP PSM from the record. 533 * @param version The Profile version number (As specified in the Bluetooth 534 * MAP specification) 535 * @param msgTypes The supported message types bit mask (As specified in 536 * the Bluetooth MAP specification) 537 * @param features The feature bit mask (As specified in the Bluetooth 538 * MAP specification) 539 * @return a handle to the record created. The record can be removed again 540 * using {@link removeSdpRecord}(). The record is not linked to the 541 * creation/destruction of BluetoothSockets, hence SDP record cleanup 542 * is a separate process. 543 */ createMapMasRecord(String serviceName, int masId, int rfcommChannel, int l2capPsm, int version, int msgTypes, int features)544 public int createMapMasRecord(String serviceName, int masId, int rfcommChannel, int l2capPsm, 545 int version, int msgTypes, int features) { 546 if (!sNativeAvailable) { 547 throw new RuntimeException(TAG + " sNativeAvailable == false - native not initialized"); 548 } 549 return sdpCreateMapMasRecordNative(serviceName, masId, rfcommChannel, l2capPsm, version, 550 msgTypes, features); 551 } 552 553 /** 554 * Create a client side Message Access Profile Service Record. 555 * Create the record once, and reuse it for all connections. 556 * If changes to a record is needed remove the old record using {@link removeSdpRecord} 557 * and then create a new one. 558 * @param serviceName The textual name of the service 559 * @param rfcommChannel The RFCOMM channel that clients can connect to 560 * (obtain from BluetoothServerSocket) 561 * @param l2capPsm The L2CAP PSM channel that clients can connect to 562 * (obtain from BluetoothServerSocket) 563 * Supply -1 to omit the L2CAP PSM from the record. 564 * @param version The Profile version number (As specified in the Bluetooth 565 * MAP specification) 566 * @param features The feature bit mask (As specified in the Bluetooth 567 * MAP specification) 568 * @return a handle to the record created. The record can be removed again 569 * using {@link removeSdpRecord}(). The record is not linked to the 570 * creation/destruction of BluetoothSockets, hence SDP record cleanup 571 * is a separate process. 572 */ createMapMnsRecord(String serviceName, int rfcommChannel, int l2capPsm, int version, int features)573 public int createMapMnsRecord(String serviceName, int rfcommChannel, int l2capPsm, int version, 574 int features) { 575 if (!sNativeAvailable) { 576 throw new RuntimeException(TAG + " sNativeAvailable == false - native not initialized"); 577 } 578 return sdpCreateMapMnsRecordNative(serviceName, rfcommChannel, l2capPsm, version, features); 579 } 580 581 /** 582 * Create a Client side Phone Book Access Profile Service Record. 583 * Create the record once, and reuse it for all connections. 584 * If changes to a record is needed remove the old record using {@link removeSdpRecord} 585 * and then create a new one. 586 * @param serviceName The textual name of the service 587 * @param version The Profile version number (As specified in the Bluetooth 588 * PBAP specification) 589 * @return a handle to the record created. The record can be removed again 590 * using {@link removeSdpRecord}(). The record is not linked to the 591 * creation/destruction of BluetoothSockets, hence SDP record cleanup 592 * is a separate process. 593 */ createPbapPceRecord(String serviceName, int version)594 public int createPbapPceRecord(String serviceName, int version) { 595 if (!sNativeAvailable) { 596 throw new RuntimeException(TAG + " sNativeAvailable == false - native not initialized"); 597 } 598 return sdpCreatePbapPceRecordNative(serviceName, version); 599 } 600 601 602 /** 603 * Create a Server side Phone Book Access Profile Service Record. 604 * Create the record once, and reuse it for all connections. 605 * If changes to a record is needed remove the old record using {@link removeSdpRecord} 606 * and then create a new one. 607 * @param serviceName The textual name of the service 608 * @param rfcommChannel The RFCOMM channel that clients can connect to 609 * (obtain from BluetoothServerSocket) 610 * @param l2capPsm The L2CAP PSM channel that clients can connect to 611 * (obtain from BluetoothServerSocket) 612 * Supply -1 to omit the L2CAP PSM from the record. 613 * @param version The Profile version number (As specified in the Bluetooth 614 * PBAP specification) 615 * @param repositories The supported repositories bit mask (As specified in 616 * the Bluetooth PBAP specification) 617 * @param features The feature bit mask (As specified in the Bluetooth 618 * PBAP specification) 619 * @return a handle to the record created. The record can be removed again 620 * using {@link removeSdpRecord}(). The record is not linked to the 621 * creation/destruction of BluetoothSockets, hence SDP record cleanup 622 * is a separate process. 623 */ createPbapPseRecord(String serviceName, int rfcommChannel, int l2capPsm, int version, int repositories, int features)624 public int createPbapPseRecord(String serviceName, int rfcommChannel, int l2capPsm, int version, 625 int repositories, int features) { 626 if (!sNativeAvailable) { 627 throw new RuntimeException(TAG + " sNativeAvailable == false - native not initialized"); 628 } 629 return sdpCreatePbapPseRecordNative(serviceName, rfcommChannel, l2capPsm, version, 630 repositories, features); 631 } 632 633 /** 634 * Create a Server side Object Push Profile Service Record. 635 * Create the record once, and reuse it for all connections. 636 * If changes to a record is needed remove the old record using {@link removeSdpRecord} 637 * and then create a new one. 638 * @param serviceName The textual name of the service 639 * @param rfcommChannel The RFCOMM channel that clients can connect to 640 * (obtain from BluetoothServerSocket) 641 * @param l2capPsm The L2CAP PSM channel that clients can connect to 642 * (obtain from BluetoothServerSocket) 643 * Supply -1 to omit the L2CAP PSM from the record. 644 * @param version The Profile version number (As specified in the Bluetooth 645 * OPP specification) 646 * @param formatsList A list of the supported formats (As specified in 647 * the Bluetooth OPP specification) 648 * @return a handle to the record created. The record can be removed again 649 * using {@link removeSdpRecord}(). The record is not linked to the 650 * creation/destruction of BluetoothSockets, hence SDP record cleanup 651 * is a separate process. 652 */ createOppOpsRecord(String serviceName, int rfcommChannel, int l2capPsm, int version, byte[] formatsList)653 public int createOppOpsRecord(String serviceName, int rfcommChannel, int l2capPsm, int version, 654 byte[] formatsList) { 655 if (!sNativeAvailable) { 656 throw new RuntimeException(TAG + " sNativeAvailable == false - native not initialized"); 657 } 658 return sdpCreateOppOpsRecordNative(serviceName, rfcommChannel, l2capPsm, version, 659 formatsList); 660 } 661 662 /** 663 * Create a server side Sim Access Profile Service Record. 664 * Create the record once, and reuse it for all connections. 665 * If changes to a record is needed remove the old record using {@link removeSdpRecord} 666 * and then create a new one. 667 * @param serviceName The textual name of the service 668 * @param rfcommChannel The RFCOMM channel that clients can connect to 669 * (obtain from BluetoothServerSocket) 670 * @param version The Profile version number (As specified in the Bluetooth 671 * SAP specification) 672 * @return a handle to the record created. The record can be removed again 673 * using {@link removeSdpRecord}(). The record is not linked to the 674 * creation/destruction of BluetoothSockets, hence SDP record cleanup 675 * is a separate process. 676 */ createSapsRecord(String serviceName, int rfcommChannel, int version)677 public int createSapsRecord(String serviceName, int rfcommChannel, int version) { 678 if (!sNativeAvailable) { 679 throw new RuntimeException(TAG + " sNativeAvailable == false - native not initialized"); 680 } 681 return sdpCreateSapsRecordNative(serviceName, rfcommChannel, version); 682 } 683 684 /** 685 * Remove a SDP record. 686 * When Bluetooth is disabled all records will be deleted, hence there 687 * is no need to call this function when bluetooth is disabled. 688 * @param recordId The Id returned by on of the createXxxXxxRecord() functions. 689 * @return TRUE if the record removal was initiated successfully. FALSE if the record 690 * handle is not known/have already been removed. 691 */ removeSdpRecord(int recordId)692 public boolean removeSdpRecord(int recordId) { 693 if (!sNativeAvailable) { 694 throw new RuntimeException(TAG + " sNativeAvailable == false - native not initialized"); 695 } 696 return sdpRemoveSdpRecordNative(recordId); 697 } 698 } 699