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