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