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