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