SdpManager.java revision 560fb676a64f888d31e672a4b66e52a05abb5f26
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*/
15package com.android.bluetooth.sdp;
16
17import android.bluetooth.BluetoothDevice;
18import android.bluetooth.SdpMasRecord;
19import android.bluetooth.SdpMnsRecord;
20import android.bluetooth.SdpOppOpsRecord;
21import android.bluetooth.SdpPseRecord;
22import android.bluetooth.SdpSapsRecord;
23import android.bluetooth.SdpRecord;
24import android.content.Intent;
25import android.os.Handler;
26import android.os.Message;
27import android.os.ParcelUuid;
28import android.os.Parcelable;
29import android.util.Log;
30
31import com.android.bluetooth.Utils;
32import com.android.bluetooth.btservice.AbstractionLayer;
33import com.android.bluetooth.btservice.AdapterService;
34
35import java.util.ArrayList;
36import java.util.Arrays;
37
38public 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