• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {
classInitNative()71         classInitNative();
72     }
73 
classInitNative()74     private native static void classInitNative();
initializeNative()75     private native void initializeNative();
cleanupNative()76     private native void cleanupNative();
sdpSearchNative(byte[] address, byte[] uuid)77     private native boolean sdpSearchNative(byte[] address, byte[] uuid);
78 
sdpCreateMapMasRecordNative(String serviceName, int masId, int rfcommChannel, int l2capPsm, int version, int msgTypes, int features)79     private native int sdpCreateMapMasRecordNative(String serviceName, int masId,
80             int rfcommChannel, int l2capPsm, int version, int msgTypes, int features);
81 
sdpCreateMapMnsRecordNative(String serviceName, int rfcommChannel, int l2capPsm, int version, int features)82     private native int sdpCreateMapMnsRecordNative(String serviceName,
83             int rfcommChannel, int l2capPsm, int version, int features);
84 
sdpCreatePbapPseRecordNative(String serviceName, int rfcommChannel, int l2capPsm, int version, int repositories, int features)85     private native int sdpCreatePbapPseRecordNative(String serviceName, int rfcommChannel,
86             int l2capPsm, int version, int repositories, int features);
87 
sdpCreateOppOpsRecordNative(String serviceName, int rfcommChannel, int l2capPsm, int version, byte[] formats_list)88     private native int sdpCreateOppOpsRecordNative(String serviceName,
89             int rfcommChannel, int l2capPsm, int version, byte[] formats_list);
90 
sdpCreateSapsRecordNative(String serviceName, int rfcommChannel, int version)91     private native int sdpCreateSapsRecordNative(String serviceName, int rfcommChannel,
92             int version);
93 
sdpRemoveSdpRecordNative(int record_id)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. */
SdpSearchInstance(int status, BluetoothDevice device, ParcelUuid uuid)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         }
getDevice()112         public BluetoothDevice getDevice() {
113             return mDevice;
114         }
getUuid()115         public ParcelUuid getUuid() {
116             return mUuid;
117         }
getStatus()118         public int getStatus(){
119             return mStatus;
120         }
121 
setStatus(int status)122         public void setStatus(int status) {
123             this.mStatus = status;
124         }
125 
startSearch()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 
stopSearch()132         public void stopSearch() {
133             if(mSearching) {
134                 mHandler.removeMessages(MESSAGE_SDP_INTENT, this);
135             }
136             mSearching = false;
137         }
isSearching()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 
clear()151         void clear() {
152             list.clear();
153         }
154 
add(SdpSearchInstance inst)155         boolean add(SdpSearchInstance inst){
156             return list.add(inst);
157         }
158 
remove(SdpSearchInstance inst)159         boolean remove(SdpSearchInstance inst) {
160             return list.remove(inst);
161         }
162 
getNext()163         SdpSearchInstance getNext() {
164             if(list.size() > 0) {
165                 return list.get(0);
166             }
167             return null;
168         }
169 
getSearchInstance(byte[] address, byte[] uuidBytes)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 
isSearching(BluetoothDevice device, ParcelUuid uuid)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 
SdpManager(AdapterService adapterService)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 
init(AdapterService adapterService)205     public static SdpManager init(AdapterService adapterService) {
206         sSdpManager = new SdpManager(adapterService);
207         return sSdpManager;
208     }
209 
getDefaultManager()210     public static SdpManager getDefaultManager() {
211         return sSdpManager;
212     }
213 
cleanup()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 
sdpMasRecordFoundCallback(int status, byte[] address, byte[] uuid, int masInstanceId, int l2capPsm, int rfcommCannelNumber, int profileVersion, int supportedFeatures, int supportedMessageTypes, String serviceName, boolean moreResults)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 
sdpMnsRecordFoundCallback(int status, byte[] address, byte[] uuid, int l2capPsm, int rfcommCannelNumber, int profileVersion, int supportedFeatures, String serviceName, boolean moreResults)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 
sdpPseRecordFoundCallback(int status, byte[] address, byte[] uuid, int l2capPsm, int rfcommCannelNumber, int profileVersion, int supportedFeatures, int supportedRepositories, String serviceName, boolean moreResults)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 
sdpOppOpsRecordFoundCallback(int status, byte[] address, byte[] uuid, int l2capPsm, int rfcommCannelNumber, int profileVersion, String serviceName, byte[] formatsList, boolean moreResults)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 
sdpSapsRecordFoundCallback(int status, byte[] address, byte[] uuid, int rfcommCannelNumber, int profileVersion, String serviceName, boolean moreResults)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! */
sdpRecordFoundCallback(int status, byte[] address, byte[] uuid, int size_record, byte[] record)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 
sdpSearch(BluetoothDevice device, ParcelUuid uuid)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 */
startSearch()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 */
sendSdpIntent(SdpSearchInstance inst, Parcelable record, boolean moreResults)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      */
createMapMasRecord(String serviceName, int masId, int rfcommChannel, int l2capPsm, int version, int msgTypes, int features)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      */
createMapMnsRecord(String serviceName, int rfcommChannel, int l2capPsm, int version, int features)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      */
createPbapPseRecord(String serviceName, int rfcommChannel, int l2capPsm, int version, int repositories, int features)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      */
createOppOpsRecord(String serviceName, int rfcommChannel, int l2capPsm, int version, byte[] formatsList)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      */
createSapsRecord(String serviceName, int rfcommChannel, int version)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       */
removeSdpRecord(int recordId)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