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