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