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