SdpManager.java revision 4aa5e4e2fe52c06d97481db0bf180349053dae48
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    // 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 = 11000;
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