/** * BlueCove - Java library for Bluetooth * Copyright (C) 2004 Intel Corporation * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. * * @version $Id$ */ package com.intel.bluetooth; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.Enumeration; import java.util.Hashtable; import javax.bluetooth.BluetoothStateException; import javax.bluetooth.DataElement; import javax.bluetooth.LocalDevice; import javax.bluetooth.RemoteDevice; import javax.bluetooth.ServiceRecord; import javax.bluetooth.UUID; import com.intel.bluetooth.BluetoothConsts.DeviceClassConsts; class ServiceRecordImpl implements ServiceRecord { private BluetoothStack bluetoothStack; private RemoteDevice device; private long handle; Hashtable attributes; protected boolean attributeUpdated; int deviceServiceClasses; int deviceServiceClassesRegistered; ServiceRecordImpl(BluetoothStack bluetoothStack, RemoteDevice device, long handle) { this.bluetoothStack = bluetoothStack; this.device = device; this.handle = handle; this.deviceServiceClassesRegistered = 0; this.attributes = new Hashtable(); } byte[] toByteArray() throws IOException { DataElement rootSeq = new DataElement(DataElement.DATSEQ); final boolean sort = true; if (sort) { int[] sortIDs = new int[attributes.size()]; int k = 0; for (Enumeration e = attributes.keys(); e.hasMoreElements();) { Integer key = (Integer) e.nextElement(); sortIDs[k] = key.intValue(); k++; } // DebugLog.debug("b4 sort", sortIDs); // Sort for (int i = 0; i < sortIDs.length; i++) { for (int j = 0; j < sortIDs.length - i - 1; j++) { if (sortIDs[j] > sortIDs[j + 1]) { int temp = sortIDs[j]; sortIDs[j] = sortIDs[j + 1]; sortIDs[j + 1] = temp; } } } // DebugLog.debug("sorted", sortIDs); for (int i = 0; i < sortIDs.length; i++) { int attrID = sortIDs[i]; rootSeq.addElement(new DataElement(DataElement.U_INT_2, attrID)); rootSeq.addElement(getAttributeValue(attrID)); } } else { for (Enumeration e = attributes.keys(); e.hasMoreElements();) { Integer key = (Integer) e.nextElement(); rootSeq.addElement(new DataElement(DataElement.U_INT_2, key.intValue())); rootSeq.addElement((DataElement) attributes.get(key)); } } ByteArrayOutputStream out = new ByteArrayOutputStream(); SDPOutputStream sdpOut = new SDPOutputStream(out); sdpOut.writeElement(rootSeq); return out.toByteArray(); } void loadByteArray(byte data[]) throws IOException { DataElement element = (new SDPInputStream(new ByteArrayInputStream(data))).readElement(); if (element.getDataType() != DataElement.DATSEQ) { throw new IOException("DATSEQ expected instead of " + element.getDataType()); } Enumeration en = (Enumeration) element.getValue(); while (en.hasMoreElements()) { DataElement id = (DataElement) en.nextElement(); if (id.getDataType() != DataElement.U_INT_2) { throw new IOException("U_INT_2 expected instead of " + id.getDataType()); } DataElement value = (DataElement) en.nextElement(); this.populateAttributeValue((int) id.getLong(), value); } } /* * Returns the value of the service attribute ID provided it is present in * the service record, otherwise this method returns null. Parameters: * attrID - the attribute whose value is to be returned Returns: the value * of the attribute ID if present in the service record, otherwise null * Throws: IllegalArgumentException - if attrID is negative or greater than * or equal to 2^16 */ public DataElement getAttributeValue(int attrID) { if (attrID < 0x0000 || attrID > 0xffff) { throw new IllegalArgumentException(); } return (DataElement) attributes.get(new Integer(attrID)); } /* * Returns the remote Bluetooth device that populated the service record * with attribute values. It is important to note that the Bluetooth device * that provided the value might not be reachable anymore, since it can * move, turn off, or change its security mode denying all further * transactions. Returns: the remote Bluetooth device that populated the * service record, or null if the local device populated this ServiceRecord */ public RemoteDevice getHostDevice() { return device; } /* * Returns the service attribute IDs whose value could be retrieved by a * call to getAttributeValue(). The list of attributes being returned is not * sorted and includes default attributes. Returns: an array of service * attribute IDs that are in this object and have values for them; if there * are no attribute IDs that have values, this method will return an array * of length zero. See Also: getAttributeValue(int) */ public int[] getAttributeIDs() { int[] attrIDs = new int[attributes.size()]; int i = 0; for (Enumeration e = attributes.keys(); e.hasMoreElements();) { attrIDs[i++] = ((Integer) e.nextElement()).intValue(); } return attrIDs; } /* * Retrieves the values by contacting the remote Bluetooth device for a set * of service attribute IDs of a service that is available on a Bluetooth * device. (This involves going over the air and contacting the remote * device for the attribute values.) The system might impose a limit on the * number of service attribute ID values one can request at a time. * Applications can obtain the value of this limit as a String by calling * LocalDevice.getProperty("bluetooth.sd.attr.retrievable.max"). The method * is blocking and will return when the results of the request are * available. Attribute IDs whose values could be obtained are added to this * service record. If there exist attribute IDs for which values are * retrieved this will cause the old values to be overwritten. If the remote * device cannot be reached, an IOException will be thrown. Parameters: * attrIDs - the list of service attributes IDs whose value are to be * retrieved; the number of attributes cannot exceed the property * bluetooth.sd.attr.retrievable.max; the attributes in the request must be * legal, i.e. their values are in the range of [0, 2^16-1]. The input * attribute IDs can include attribute IDs from the default attribute set * too. Returns: true if the request was successful in retrieving values for * some or all of the attribute IDs; false if it was unsuccessful in * retrieving any values Throws: java.io.IOException - if the local device * is unable to connect to the remote Bluetooth device that was the source * of this ServiceRecord; if this ServiceRecord was deleted from the SDDB of * the remote device IllegalArgumentException - if the size of attrIDs * exceeds the system specified limit as defined by * bluetooth.sd.attr.retrievable.max; if the attrIDs array length is zero; * if any of their values are not in the range of [0, 2^16-1]; if attrIDs * has duplicate values NullPointerException - if attrIDs is null * RuntimeException - if this ServiceRecord describes a service on the local * device rather than a service on a remote device */ public boolean populateRecord(int[] attrIDs) throws IOException { /* * check this is not a local service record */ if (device == null) { throw new RuntimeException("This is local device service record"); } if (attrIDs == null) { throw new NullPointerException("attrIDs is null"); } /* * check attrIDs is non-null and has length > 0 */ if (attrIDs.length == 0) { throw new IllegalArgumentException(); } /* * check attrIDs are in range */ for (int i = 0; i < attrIDs.length; i++) { if (attrIDs[i] < 0x0000 || attrIDs[i] > 0xffff) { throw new IllegalArgumentException(); } } /* * copy and sort attrIDs (required by MS Bluetooth and for check for * duplicates) */ int[] sortIDs = new int[attrIDs.length]; System.arraycopy(attrIDs, 0, sortIDs, 0, attrIDs.length); for (int i = 0; i < sortIDs.length; i++) { for (int j = 0; j < sortIDs.length - i - 1; j++) { if (sortIDs[j] > sortIDs[j + 1]) { int temp = sortIDs[j]; sortIDs[j] = sortIDs[j + 1]; sortIDs[j + 1] = temp; } } } /* * check for duplicates */ for (int i = 0; i < sortIDs.length - 1; i++) { if (sortIDs[i] == sortIDs[i + 1]) { throw new IllegalArgumentException(); } DebugLog.debug0x("srvRec query for attr", sortIDs[i]); } DebugLog.debug0x("srvRec query for attr", sortIDs[sortIDs.length - 1]); return this.bluetoothStack.populateServicesRecordAttributeValues(this, sortIDs); } /* * Returns a String including optional parameters that can be used by a * client to connect to the service described by this ServiceRecord. The * return value can be used as the first argument to Connector.open(). In * the case of a Serial Port service record, this string might look like * "btspp://0050CD00321B:3;authenticate=true;encrypt=false;master=true", * where "0050CD00321B" is the Bluetooth address of the device that provided * this ServiceRecord, "3" is the RFCOMM server channel mentioned in this * ServiceRecord, and there are three optional parameters related to * security and master/slave roles. If this method is called on a * ServiceRecord returned from LocalDevice.getRecord(), it will return the * connection string that a remote device will use to connect to this * service. * * Parameters: requiredSecurity - determines whether authentication or * encryption are required for a connection mustBeMaster - true indicates * that this device must play the role of master in connections to this * service; false indicates that the local device is willing to be either * the master or the slave Returns: a string that can be used to connect to * the service or null if the ProtocolDescriptorList in this ServiceRecord * is not formatted according to the Bluetooth specification Throws: * IllegalArgumentException - if requiredSecurity is not one of the * constants NOAUTHENTICATE_NOENCRYPT, AUTHENTICATE_NOENCRYPT, or * AUTHENTICATE_ENCRYPT See Also: NOAUTHENTICATE_NOENCRYPT, * AUTHENTICATE_NOENCRYPT, AUTHENTICATE_ENCRYPT */ public String getConnectionURL(int requiredSecurity, boolean mustBeMaster) { int commChannel = -1; DataElement protocolDescriptor = getAttributeValue(BluetoothConsts.ProtocolDescriptorList); if ((protocolDescriptor == null) || (protocolDescriptor.getDataType() != DataElement.DATSEQ)) { return null; } /* * get RFCOMM Channel ProtocolDescriptorList is DATSEQ of DATSEQ of UUID * and optional parameters */ boolean isL2CAP = false; boolean isRFCOMM = false; boolean isOBEX = false; for (Enumeration protocolsSeqEnum = (Enumeration) protocolDescriptor.getValue(); protocolsSeqEnum .hasMoreElements();) { DataElement elementSeq = (DataElement) protocolsSeqEnum.nextElement(); if (elementSeq.getDataType() == DataElement.DATSEQ) { Enumeration elementSeqEnum = (Enumeration) elementSeq.getValue(); if (elementSeqEnum.hasMoreElements()) { DataElement protocolElement = (DataElement) elementSeqEnum.nextElement(); if (protocolElement.getDataType() != DataElement.UUID) { continue; } Object uuid = protocolElement.getValue(); if (BluetoothConsts.OBEX_PROTOCOL_UUID.equals(uuid)) { isOBEX = true; isRFCOMM = false; isL2CAP = false; } else if (elementSeqEnum.hasMoreElements() && (BluetoothConsts.RFCOMM_PROTOCOL_UUID.equals(uuid))) { DataElement protocolPSMElement = (DataElement) elementSeqEnum.nextElement(); switch (protocolPSMElement.getDataType()) { case DataElement.U_INT_1: case DataElement.U_INT_2: case DataElement.U_INT_4: case DataElement.INT_1: case DataElement.INT_2: case DataElement.INT_4: case DataElement.INT_8: long val = protocolPSMElement.getLong(); if ((val >= BluetoothConsts.RFCOMM_CHANNEL_MIN) && (val <= BluetoothConsts.RFCOMM_CHANNEL_MAX)) { commChannel = (int) val; isRFCOMM = true; isL2CAP = false; } break; } } else if (elementSeqEnum.hasMoreElements() && (BluetoothConsts.L2CAP_PROTOCOL_UUID.equals(uuid))) { DataElement protocolPSMElement = (DataElement) elementSeqEnum.nextElement(); switch (protocolPSMElement.getDataType()) { case DataElement.U_INT_1: case DataElement.U_INT_2: case DataElement.U_INT_4: case DataElement.INT_1: case DataElement.INT_2: case DataElement.INT_4: case DataElement.INT_8: long pcm = protocolPSMElement.getLong(); if ((pcm >= BluetoothConsts.L2CAP_PSM_MIN) && (pcm <= BluetoothConsts.L2CAP_PSM_MAX)) { commChannel = (int) pcm; isL2CAP = true; } break; } } } } } if (commChannel == -1) { return null; } /* * build URL */ StringBuffer buf = new StringBuffer(); if (isOBEX) { buf.append(BluetoothConsts.PROTOCOL_SCHEME_BT_OBEX); } else if (isRFCOMM) { buf.append(BluetoothConsts.PROTOCOL_SCHEME_RFCOMM); } else if (isL2CAP) { buf.append(BluetoothConsts.PROTOCOL_SCHEME_L2CAP); } else { return null; } buf.append("://"); if (device == null) { try { Object saveID = BlueCoveImpl.getCurrentThreadBluetoothStackID(); try { BlueCoveImpl.setThreadBluetoothStack(bluetoothStack); buf.append(LocalDevice.getLocalDevice().getBluetoothAddress()); } finally { if (saveID != null) { BlueCoveImpl.setThreadBluetoothStackID(saveID); } } } catch (BluetoothStateException bse) { DebugLog.error("can't read LocalAddress", bse); buf.append("localhost"); } } else { buf.append(getHostDevice().getBluetoothAddress()); } buf.append(":"); if (isL2CAP) { String hex = Integer.toHexString(commChannel); for (int i = hex.length(); i < 4; i++) { buf.append('0'); } buf.append(hex); } else { buf.append(commChannel); } switch (requiredSecurity) { case NOAUTHENTICATE_NOENCRYPT: buf.append(";authenticate=false;encrypt=false"); break; case AUTHENTICATE_NOENCRYPT: buf.append(";authenticate=true;encrypt=false"); break; case AUTHENTICATE_ENCRYPT: buf.append(";authenticate=true;encrypt=true"); break; default: throw new IllegalArgumentException(); } if (mustBeMaster) { buf.append(";master=true"); } else { buf.append(";master=false"); } return buf.toString(); } int getChannel(UUID protocolUUID) { int channel = -1; DataElement protocolDescriptor = getAttributeValue(BluetoothConsts.ProtocolDescriptorList); if ((protocolDescriptor == null) || (protocolDescriptor.getDataType() != DataElement.DATSEQ)) { return -1; } /* * get RFCOMM Channel or L2CAP PSM ProtocolDescriptorList is DATSEQ of * DATSEQ of UUID and optional parameters */ for (Enumeration protocolsSeqEnum = (Enumeration) protocolDescriptor.getValue(); protocolsSeqEnum .hasMoreElements();) { DataElement elementSeq = (DataElement) protocolsSeqEnum.nextElement(); if (elementSeq.getDataType() == DataElement.DATSEQ) { Enumeration elementSeqEnum = (Enumeration) elementSeq.getValue(); if (elementSeqEnum.hasMoreElements()) { DataElement protocolElement = (DataElement) elementSeqEnum.nextElement(); if (protocolElement.getDataType() != DataElement.UUID) { continue; } Object uuid = protocolElement.getValue(); if (elementSeqEnum.hasMoreElements() && (protocolUUID.equals(uuid))) { DataElement protocolPSMElement = (DataElement) elementSeqEnum.nextElement(); switch (protocolPSMElement.getDataType()) { case DataElement.U_INT_1: case DataElement.U_INT_2: case DataElement.U_INT_4: case DataElement.INT_1: case DataElement.INT_2: case DataElement.INT_4: case DataElement.INT_8: channel = (int) protocolPSMElement.getLong(); break; } } } } } return channel; } /* * Used by a server application to indicate the major service class bits * that should be activated in the server's DeviceClass when this * ServiceRecord is added to the SDDB. When client devices do device * discovery, the server's DeviceClass is provided as one of the arguments * of the deviceDiscovered method of the DiscoveryListener interface. Client * devices can consult the DeviceClass of the server device to get a general * idea of the kind of device this is (e.g., phone, PDA, or PC) and the * major service classes it offers (e.g., rendering, telephony, or * information). A server application should use the setDeviceServiceClasses * method to describe its service in terms of the major service classes. * This allows clients to obtain a DeviceClass for the server that * accurately describes all of the services being offered. When * acceptAndOpen() is invoked for the first time on the notifier associated * with this ServiceRecord, the classes argument from the * setDeviceServiceClasses method is OR'ed with the current setting of the * major service class bits of the local device. The OR operation * potentially activates additional bits. These bits may be retrieved by * calling getDeviceClass() on the LocalDevice object. Likewise, a call to * LocalDevice.updateRecord() will cause the major service class bits to be * OR'ed with the current settings and updated. * * The documentation for DeviceClass gives examples of the integers that * describe each of the major service classes and provides a URL for the * complete list. These integers can be used individually or OR'ed together * to describe the appropriate value for classes. * * Later, when this ServiceRecord is removed from the SDDB, the * implementation will automatically deactivate the device bits that were * activated as a result of the call to setDeviceServiceClasses. The only * exception to this occurs if there is another ServiceRecord that is in the * SDDB and setDeviceServiceClasses has been sent to that other * ServiceRecord to request that some of the same bits be activated. * * Parameters: classes - an integer whose binary representation indicates * the major service class bits that should be activated Throws: * IllegalArgumentException - if classes is not an OR of one or more of the * major service class integers in the Bluetooth Assigned Numbers document. * While Limited Discoverable Mode is included in this list of major service * classes, its bit is activated by placing the device in Limited * Discoverable Mode (see the GAP specification), so if bit 13 is set this * exception will be thrown. RuntimeExceptin - if the ServiceRecord * receiving the message was obtained from a remote device */ public void setDeviceServiceClasses(int classes) { if (device != null) { throw new RuntimeException("Service record obtained from a remote device"); } if ((classes & (0xff000000 | DeviceClassConsts.LIMITED_DISCOVERY_SERVICE | DeviceClassConsts.FORMAT_VERSION_MASK)) != 0) { throw new IllegalArgumentException(); } if ((classes & (DeviceClassConsts.MAJOR_MASK | DeviceClassConsts.MINOR_MASK)) != 0) { throw new IllegalArgumentException(); } if ((bluetoothStack.getFeatureSet() & BluetoothStack.FEATURE_SET_DEVICE_SERVICE_CLASSES) == 0) { throw new NotSupportedRuntimeException(bluetoothStack.getStackID()); } this.deviceServiceClasses = classes; } /* * Modifies this ServiceRecord to contain the service attribute defined by * the attribute-value pair (attrID, attrValue). If the attrID does not * exist in the ServiceRecord, this attribute-value pair is added to this * ServiceRecord object. If the attrID is already in this ServiceRecord, the * value of the attribute is changed to attrValue. If attrValue is null, the * attribute with the attribute ID of attrID is removed from this * ServiceRecord object. If attrValue is null and attrID does not exist in * this object, this method will return false. This method makes no * modifications to a service record in the SDDB. In order for any changes * made by this method to be reflected in the SDDB, a call must be made to * the acceptAndOpen() method of the associated notifier to add this * ServiceRecord to the SDDB for the first time, or a call must be made to * the updateRecord() method of LocalDevice to modify the version of this * ServiceRecord that is already in the SDDB. * * This method prevents the ServiceRecordHandle from being modified by * throwing an IllegalArgumentException. * * Parameters: attrID - the service attribute ID attrValue - the DataElement * which is the value of the service attribute Returns: true if the service * attribute was successfully added, removed, or modified; false if * attrValue is null and attrID is not in this object Throws: * IllegalArgumentException - if attrID does not represent a 16-bit unsigned * integer; if attrID is the value of ServiceRecordHandle (0x0000) * RuntimeException - if this method is called on a ServiceRecord that was * created by a call to DiscoveryAgent.searchServices() */ public boolean setAttributeValue(int attrID, DataElement attrValue) { /* * check this is a local service record */ if (device != null) { throw new IllegalArgumentException(); } if (attrID < 0x0000 || attrID > 0xffff) { throw new IllegalArgumentException(); } if (attrID == BluetoothConsts.ServiceRecordHandle) { throw new IllegalArgumentException(); } /* * remove, add or modify attribute */ attributeUpdated = true; if (attrValue == null) { return (attributes.remove(new Integer(attrID)) != null); } else { attributes.put(new Integer(attrID), attrValue); return true; } } /** * Internal implementation function */ void populateAttributeValue(int attrID, DataElement attrValue) { if (attrID < 0x0000 || attrID > 0xffff) { throw new IllegalArgumentException(); } if (attrValue == null) { attributes.remove(new Integer(attrID)); } else { attributes.put(new Integer(attrID), attrValue); } } public String toString() { StringBuffer buf = new StringBuffer("{\n"); for (Enumeration e = attributes.keys(); e.hasMoreElements();) { Integer i = (Integer) e.nextElement(); buf.append("0x"); buf.append(Integer.toHexString(i.intValue())); buf.append(":\n\t"); DataElement d = (DataElement) attributes.get(i); buf.append(d); buf.append("\n"); } buf.append("}"); return buf.toString(); } /** * Internal implementation function */ long getHandle() { return this.handle; } /** * Internal implementation function */ void setHandle(long handle) { this.handle = handle; } /** * Internal implementation function */ boolean hasServiceClassUUID(UUID uuid) { DataElement attrDataElement = getAttributeValue(BluetoothConsts.ServiceClassIDList); if ((attrDataElement == null) || (attrDataElement.getDataType() != DataElement.DATSEQ) || attrDataElement.getSize() == 0) { // DebugLog.debug("Bogus ServiceClassIDList"); return false; } Object value = attrDataElement.getValue(); if ((value == null) || (!(value instanceof Enumeration))) { DebugLog.debug("Bogus Value in DATSEQ"); if (value != null) { DebugLog.error("DATSEQ class " + value.getClass().getName()); } return false; } for (Enumeration e = (Enumeration) value; e.hasMoreElements();) { Object element = e.nextElement(); if (!(element instanceof DataElement)) { DebugLog.debug("Bogus element in DATSEQ, " + value.getClass().getName()); continue; } DataElement dataElement = (DataElement) element; if ((dataElement.getDataType() == DataElement.UUID) && (uuid.equals(dataElement.getValue()))) { return true; } } return false; } boolean hasProtocolClassUUID(UUID uuid) { DataElement protocolDescriptor = getAttributeValue(BluetoothConsts.ProtocolDescriptorList); if ((protocolDescriptor == null) || (protocolDescriptor.getDataType() != DataElement.DATSEQ)) { // DebugLog.debug("Bogus ProtocolDescriptorList"); return false; } for (Enumeration protocolsSeqEnum = (Enumeration) protocolDescriptor.getValue(); protocolsSeqEnum .hasMoreElements();) { DataElement elementSeq = (DataElement) protocolsSeqEnum.nextElement(); if (elementSeq.getDataType() == DataElement.DATSEQ) { Enumeration elementSeqEnum = (Enumeration) elementSeq.getValue(); if (elementSeqEnum.hasMoreElements()) { DataElement protocolElement = (DataElement) elementSeqEnum.nextElement(); if (protocolElement.getDataType() != DataElement.UUID) { continue; } if (uuid.equals(protocolElement.getValue())) { return true; } } } } return false; } DataElement clone(DataElement de) { DataElement c = null; switch (de.getDataType()) { case DataElement.U_INT_1: case DataElement.U_INT_2: case DataElement.U_INT_4: case DataElement.INT_1: case DataElement.INT_2: case DataElement.INT_4: c = new DataElement(de.getDataType(), de.getLong()); break; case DataElement.URL: case DataElement.STRING: case DataElement.UUID: case DataElement.INT_16: case DataElement.INT_8: case DataElement.U_INT_16: c = new DataElement(de.getDataType(), de.getValue()); break; case DataElement.NULL: c = new DataElement(de.getDataType()); break; case DataElement.BOOL: c = new DataElement(de.getBoolean()); break; case DataElement.DATSEQ: case DataElement.DATALT: c = new DataElement(de.getDataType()); for (Enumeration en = (Enumeration) de.getValue(); en.hasMoreElements();) { DataElement dataElement = (DataElement) en.nextElement(); c.addElement(clone(dataElement)); } } return c; } /** * Internal implementation function */ void populateRFCOMMAttributes(long handle, int channel, UUID uuid, String name, boolean obex) { this.populateAttributeValue(BluetoothConsts.ServiceRecordHandle, new DataElement(DataElement.U_INT_4, handle)); /* * service class ID list */ DataElement serviceClassIDList = new DataElement(DataElement.DATSEQ); serviceClassIDList.addElement(new DataElement(DataElement.UUID, uuid)); if (!obex) { serviceClassIDList.addElement(new DataElement(DataElement.UUID, BluetoothConsts.SERIAL_PORT_UUID)); } this.populateAttributeValue(BluetoothConsts.ServiceClassIDList, serviceClassIDList); /* * protocol descriptor list */ DataElement protocolDescriptorList = new DataElement(DataElement.DATSEQ); DataElement L2CAPDescriptor = new DataElement(DataElement.DATSEQ); L2CAPDescriptor.addElement(new DataElement(DataElement.UUID, BluetoothConsts.L2CAP_PROTOCOL_UUID)); protocolDescriptorList.addElement(L2CAPDescriptor); DataElement RFCOMMDescriptor = new DataElement(DataElement.DATSEQ); RFCOMMDescriptor.addElement(new DataElement(DataElement.UUID, BluetoothConsts.RFCOMM_PROTOCOL_UUID)); RFCOMMDescriptor.addElement(new DataElement(DataElement.U_INT_1, channel)); protocolDescriptorList.addElement(RFCOMMDescriptor); if (obex) { DataElement OBEXDescriptor = new DataElement(DataElement.DATSEQ); OBEXDescriptor.addElement(new DataElement(DataElement.UUID, BluetoothConsts.OBEX_PROTOCOL_UUID)); protocolDescriptorList.addElement(OBEXDescriptor); } this.populateAttributeValue(BluetoothConsts.ProtocolDescriptorList, protocolDescriptorList); if (name != null) { this.populateAttributeValue(BluetoothConsts.AttributeIDServiceName, new DataElement(DataElement.STRING, name)); } } void populateL2CAPAttributes(int handle, int channel, UUID uuid, String name) { this.populateAttributeValue(BluetoothConsts.ServiceRecordHandle, new DataElement(DataElement.U_INT_4, handle)); /* * service class ID list */ DataElement serviceClassIDList = new DataElement(DataElement.DATSEQ); serviceClassIDList.addElement(new DataElement(DataElement.UUID, uuid)); this.populateAttributeValue(BluetoothConsts.ServiceClassIDList, serviceClassIDList); /* * protocol descriptor list */ DataElement protocolDescriptorList = new DataElement(DataElement.DATSEQ); DataElement L2CAPDescriptor = new DataElement(DataElement.DATSEQ); L2CAPDescriptor.addElement(new DataElement(DataElement.UUID, BluetoothConsts.L2CAP_PROTOCOL_UUID)); L2CAPDescriptor.addElement(new DataElement(DataElement.U_INT_2, channel)); protocolDescriptorList.addElement(L2CAPDescriptor); this.populateAttributeValue(BluetoothConsts.ProtocolDescriptorList, protocolDescriptorList); if (name != null) { this.populateAttributeValue(BluetoothConsts.AttributeIDServiceName, new DataElement(DataElement.STRING, name)); } } }