View Javadoc

1   /**
2    *  BlueCove - Java library for Bluetooth
3    *  Copyright (C) 2004 Intel Corporation
4    *
5    *  Licensed to the Apache Software Foundation (ASF) under one
6    *  or more contributor license agreements.  See the NOTICE file
7    *  distributed with this work for additional information
8    *  regarding copyright ownership.  The ASF licenses this file
9    *  to you under the Apache License, Version 2.0 (the
10   *  "License"); you may not use this file except in compliance
11   *  with the License.  You may obtain a copy of the License at
12   *
13   *    http://www.apache.org/licenses/LICENSE-2.0
14   *
15   *  Unless required by applicable law or agreed to in writing,
16   *  software distributed under the License is distributed on an
17   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18   *  KIND, either express or implied.  See the License for the
19   *  specific language governing permissions and limitations
20   *  under the License.
21   *
22   *  @version $Id: ServiceRecordImpl.java 2624 2008-12-19 17:16:25Z skarzhevskyy $
23   */
24  package com.intel.bluetooth;
25  
26  import java.io.ByteArrayInputStream;
27  import java.io.ByteArrayOutputStream;
28  import java.io.IOException;
29  import java.util.Enumeration;
30  import java.util.Hashtable;
31  
32  import javax.bluetooth.BluetoothStateException;
33  import javax.bluetooth.DataElement;
34  import javax.bluetooth.LocalDevice;
35  import javax.bluetooth.RemoteDevice;
36  import javax.bluetooth.ServiceRecord;
37  import javax.bluetooth.UUID;
38  
39  import com.intel.bluetooth.BluetoothConsts.DeviceClassConsts;
40  
41  class ServiceRecordImpl implements ServiceRecord {
42  
43  	private BluetoothStack bluetoothStack;
44  
45  	private RemoteDevice device;
46  
47  	private long handle;
48  
49  	Hashtable attributes;
50  
51  	protected boolean attributeUpdated;
52  
53  	int deviceServiceClasses;
54  
55  	int deviceServiceClassesRegistered;
56  
57  	ServiceRecordImpl(BluetoothStack bluetoothStack, RemoteDevice device, long handle) {
58  
59  		this.bluetoothStack = bluetoothStack;
60  
61  		this.device = device;
62  
63  		this.handle = handle;
64  
65  		this.deviceServiceClassesRegistered = 0;
66  
67  		this.attributes = new Hashtable();
68  	}
69  
70  	byte[] toByteArray() throws IOException {
71  		DataElement element = new DataElement(DataElement.DATSEQ);
72  
73  		final boolean sort = true;
74  		if (sort) {
75  			int[] sortIDs = new int[attributes.size()];
76  			int k = 0;
77  			for (Enumeration e = attributes.keys(); e.hasMoreElements();) {
78  				Integer key = (Integer) e.nextElement();
79  				sortIDs[k] = key.intValue();
80  				k++;
81  			}
82  			// DebugLog.debug("b4 sort", sortIDs);
83  			// Sort
84  			for (int i = 0; i < sortIDs.length; i++) {
85  				for (int j = 0; j < sortIDs.length - i - 1; j++) {
86  					if (sortIDs[j] > sortIDs[j + 1]) {
87  						int temp = sortIDs[j];
88  						sortIDs[j] = sortIDs[j + 1];
89  						sortIDs[j + 1] = temp;
90  					}
91  				}
92  			}
93  			// DebugLog.debug("sorted", sortIDs);
94  
95  			for (int i = 0; i < sortIDs.length; i++) {
96  				element.addElement(new DataElement(DataElement.U_INT_2, sortIDs[i]));
97  				element.addElement(getAttributeValue(sortIDs[i]));
98  			}
99  		} else {
100 			for (Enumeration e = attributes.keys(); e.hasMoreElements();) {
101 				Integer key = (Integer) e.nextElement();
102 
103 				element.addElement(new DataElement(DataElement.U_INT_2, key.intValue()));
104 				element.addElement((DataElement) attributes.get(key));
105 			}
106 		}
107 
108 		ByteArrayOutputStream out = new ByteArrayOutputStream();
109 
110 		(new SDPOutputStream(out)).writeElement(element);
111 
112 		return out.toByteArray();
113 	}
114 
115 	void loadByteArray(byte data[]) throws IOException {
116 		DataElement element = (new SDPInputStream(new ByteArrayInputStream(data))).readElement();
117 		if (element.getDataType() != DataElement.DATSEQ) {
118 			throw new IOException("DATSEQ expected instead of " + element.getDataType());
119 		}
120 		Enumeration en = (Enumeration) element.getValue();
121 		while (en.hasMoreElements()) {
122 			DataElement id = (DataElement) en.nextElement();
123 			if (id.getDataType() != DataElement.U_INT_2) {
124 				throw new IOException("U_INT_2 expected instead of " + id.getDataType());
125 			}
126 			DataElement value = (DataElement) en.nextElement();
127 			this.populateAttributeValue((int) id.getLong(), value);
128 		}
129 	}
130 
131 	/*
132 	 * Returns the value of the service attribute ID provided it is present in
133 	 * the service record, otherwise this method returns null. Parameters:
134 	 * attrID - the attribute whose value is to be returned Returns: the value
135 	 * of the attribute ID if present in the service record, otherwise null
136 	 * Throws: IllegalArgumentException - if attrID is negative or greater than
137 	 * or equal to 2^16
138 	 */
139 
140 	public DataElement getAttributeValue(int attrID) {
141 		if (attrID < 0x0000 || attrID > 0xffff) {
142 			throw new IllegalArgumentException();
143 		}
144 
145 		return (DataElement) attributes.get(new Integer(attrID));
146 	}
147 
148 	/*
149 	 * Returns the remote Bluetooth device that populated the service record
150 	 * with attribute values. It is important to note that the Bluetooth device
151 	 * that provided the value might not be reachable anymore, since it can
152 	 * move, turn off, or change its security mode denying all further
153 	 * transactions. Returns: the remote Bluetooth device that populated the
154 	 * service record, or null if the local device populated this ServiceRecord
155 	 */
156 
157 	public RemoteDevice getHostDevice() {
158 		return device;
159 	}
160 
161 	/*
162 	 * Returns the service attribute IDs whose value could be retrieved by a
163 	 * call to getAttributeValue(). The list of attributes being returned is not
164 	 * sorted and includes default attributes. Returns: an array of service
165 	 * attribute IDs that are in this object and have values for them; if there
166 	 * are no attribute IDs that have values, this method will return an array
167 	 * of length zero. See Also: getAttributeValue(int)
168 	 */
169 
170 	public int[] getAttributeIDs() {
171 		int[] attrIDs = new int[attributes.size()];
172 
173 		int i = 0;
174 
175 		for (Enumeration e = attributes.keys(); e.hasMoreElements();) {
176 			attrIDs[i++] = ((Integer) e.nextElement()).intValue();
177 		}
178 
179 		return attrIDs;
180 	}
181 
182 	/*
183 	 * Retrieves the values by contacting the remote Bluetooth device for a set
184 	 * of service attribute IDs of a service that is available on a Bluetooth
185 	 * device. (This involves going over the air and contacting the remote
186 	 * device for the attribute values.) The system might impose a limit on the
187 	 * number of service attribute ID values one can request at a time.
188 	 * Applications can obtain the value of this limit as a String by calling
189 	 * LocalDevice.getProperty("bluetooth.sd.attr.retrievable.max"). The method
190 	 * is blocking and will return when the results of the request are
191 	 * available. Attribute IDs whose values could be obtained are added to this
192 	 * service record. If there exist attribute IDs for which values are
193 	 * retrieved this will cause the old values to be overwritten. If the remote
194 	 * device cannot be reached, an IOException will be thrown. Parameters:
195 	 * attrIDs - the list of service attributes IDs whose value are to be
196 	 * retrieved; the number of attributes cannot exceed the property
197 	 * bluetooth.sd.attr.retrievable.max; the attributes in the request must be
198 	 * legal, i.e. their values are in the range of [0, 2^16-1]. The input
199 	 * attribute IDs can include attribute IDs from the default attribute set
200 	 * too. Returns: true if the request was successful in retrieving values for
201 	 * some or all of the attribute IDs; false if it was unsuccessful in
202 	 * retrieving any values Throws: java.io.IOException - if the local device
203 	 * is unable to connect to the remote Bluetooth device that was the source
204 	 * of this ServiceRecord; if this ServiceRecord was deleted from the SDDB of
205 	 * the remote device IllegalArgumentException - if the size of attrIDs
206 	 * exceeds the system specified limit as defined by
207 	 * bluetooth.sd.attr.retrievable.max; if the attrIDs array length is zero;
208 	 * if any of their values are not in the range of [0, 2^16-1]; if attrIDs
209 	 * has duplicate values NullPointerException - if attrIDs is null
210 	 * RuntimeException - if this ServiceRecord describes a service on the local
211 	 * device rather than a service on a remote device
212 	 */
213 
214 	public boolean populateRecord(int[] attrIDs) throws IOException {
215 		/*
216 		 * check this is not a local service record
217 		 */
218 		if (device == null) {
219 			throw new RuntimeException("This is local device service record");
220 		}
221 
222 		if (attrIDs == null) {
223 			throw new NullPointerException("attrIDs is null");
224 		}
225 		/*
226 		 * check attrIDs is non-null and has length > 0
227 		 */
228 		if (attrIDs.length == 0) {
229 			throw new IllegalArgumentException();
230 		}
231 
232 		/*
233 		 * check attrIDs are in range
234 		 */
235 
236 		for (int i = 0; i < attrIDs.length; i++) {
237 			if (attrIDs[i] < 0x0000 || attrIDs[i] > 0xffff) {
238 				throw new IllegalArgumentException();
239 			}
240 		}
241 
242 		/*
243 		 * copy and sort attrIDs (required by MS Bluetooth and for check for
244 		 * duplicates)
245 		 */
246 
247 		int[] sortIDs = new int[attrIDs.length];
248 		System.arraycopy(attrIDs, 0, sortIDs, 0, attrIDs.length);
249 		for (int i = 0; i < sortIDs.length; i++) {
250 			for (int j = 0; j < sortIDs.length - i - 1; j++) {
251 				if (sortIDs[j] > sortIDs[j + 1]) {
252 					int temp = sortIDs[j];
253 					sortIDs[j] = sortIDs[j + 1];
254 					sortIDs[j + 1] = temp;
255 				}
256 			}
257 		}
258 		/*
259 		 * check for duplicates
260 		 */
261 		for (int i = 0; i < sortIDs.length - 1; i++) {
262 			if (sortIDs[i] == sortIDs[i + 1]) {
263 				throw new IllegalArgumentException();
264 			}
265 			DebugLog.debug0x("srvRec query for attr", sortIDs[i]);
266 		}
267 		DebugLog.debug0x("srvRec query for attr", sortIDs[sortIDs.length - 1]);
268 
269 		return this.bluetoothStack.populateServicesRecordAttributeValues(this, sortIDs);
270 	}
271 
272 	/*
273 	 * Returns a String including optional parameters that can be used by a
274 	 * client to connect to the service described by this ServiceRecord. The
275 	 * return value can be used as the first argument to Connector.open(). In
276 	 * the case of a Serial Port service record, this string might look like
277 	 * "btspp://0050CD00321B:3;authenticate=true;encrypt=false;master=true",
278 	 * where "0050CD00321B" is the Bluetooth address of the device that provided
279 	 * this ServiceRecord, "3" is the RFCOMM server channel mentioned in this
280 	 * ServiceRecord, and there are three optional parameters related to
281 	 * security and master/slave roles. If this method is called on a
282 	 * ServiceRecord returned from LocalDevice.getRecord(), it will return the
283 	 * connection string that a remote device will use to connect to this
284 	 * service.
285 	 *
286 	 * Parameters: requiredSecurity - determines whether authentication or
287 	 * encryption are required for a connection mustBeMaster - true indicates
288 	 * that this device must play the role of master in connections to this
289 	 * service; false indicates that the local device is willing to be either
290 	 * the master or the slave Returns: a string that can be used to connect to
291 	 * the service or null if the ProtocolDescriptorList in this ServiceRecord
292 	 * is not formatted according to the Bluetooth specification Throws:
293 	 * IllegalArgumentException - if requiredSecurity is not one of the
294 	 * constants NOAUTHENTICATE_NOENCRYPT, AUTHENTICATE_NOENCRYPT, or
295 	 * AUTHENTICATE_ENCRYPT See Also: NOAUTHENTICATE_NOENCRYPT,
296 	 * AUTHENTICATE_NOENCRYPT, AUTHENTICATE_ENCRYPT
297 	 */
298 
299 	public String getConnectionURL(int requiredSecurity, boolean mustBeMaster) {
300 
301 		int commChannel = -1;
302 
303 		DataElement protocolDescriptor = getAttributeValue(BluetoothConsts.ProtocolDescriptorList);
304 		if ((protocolDescriptor == null) || (protocolDescriptor.getDataType() != DataElement.DATSEQ)) {
305 			return null;
306 		}
307 
308 		/*
309 		 * get RFCOMM Channel ProtocolDescriptorList is DATSEQ of DATSEQ of UUID
310 		 * and optional parameters
311 		 */
312 
313 		boolean isL2CAP = false;
314 		boolean isRFCOMM = false;
315 		boolean isOBEX = false;
316 
317 		for (Enumeration protocolsSeqEnum = (Enumeration) protocolDescriptor.getValue(); protocolsSeqEnum
318 				.hasMoreElements();) {
319 			DataElement elementSeq = (DataElement) protocolsSeqEnum.nextElement();
320 
321 			if (elementSeq.getDataType() == DataElement.DATSEQ) {
322 				Enumeration elementSeqEnum = (Enumeration) elementSeq.getValue();
323 
324 				if (elementSeqEnum.hasMoreElements()) {
325 					DataElement protocolElement = (DataElement) elementSeqEnum.nextElement();
326 					if (protocolElement.getDataType() != DataElement.UUID) {
327 						continue;
328 					}
329 					Object uuid = protocolElement.getValue();
330 					if (BluetoothConsts.OBEX_PROTOCOL_UUID.equals(uuid)) {
331 						isOBEX = true;
332 						isRFCOMM = false;
333 						isL2CAP = false;
334 					} else if (elementSeqEnum.hasMoreElements() && (BluetoothConsts.RFCOMM_PROTOCOL_UUID.equals(uuid))) {
335 
336 						DataElement protocolPSMElement = (DataElement) elementSeqEnum.nextElement();
337 
338 						switch (protocolPSMElement.getDataType()) {
339 						case DataElement.U_INT_1:
340 						case DataElement.U_INT_2:
341 						case DataElement.U_INT_4:
342 						case DataElement.INT_1:
343 						case DataElement.INT_2:
344 						case DataElement.INT_4:
345 						case DataElement.INT_8:
346 							long val = protocolPSMElement.getLong();
347 							if ((val >= BluetoothConsts.RFCOMM_CHANNEL_MIN)
348 									&& (val <= BluetoothConsts.RFCOMM_CHANNEL_MAX)) {
349 								commChannel = (int) val;
350 								isRFCOMM = true;
351 								isL2CAP = false;
352 							}
353 							break;
354 						}
355 					} else if (elementSeqEnum.hasMoreElements() && (BluetoothConsts.L2CAP_PROTOCOL_UUID.equals(uuid))) {
356 						DataElement protocolPSMElement = (DataElement) elementSeqEnum.nextElement();
357 						switch (protocolPSMElement.getDataType()) {
358 						case DataElement.U_INT_1:
359 						case DataElement.U_INT_2:
360 						case DataElement.U_INT_4:
361 						case DataElement.INT_1:
362 						case DataElement.INT_2:
363 						case DataElement.INT_4:
364 						case DataElement.INT_8:
365 							long pcm = protocolPSMElement.getLong();
366 							if ((pcm >= BluetoothConsts.L2CAP_PSM_MIN) && (pcm <= BluetoothConsts.L2CAP_PSM_MAX)) {
367 								commChannel = (int) pcm;
368 								isL2CAP = true;
369 							}
370 							break;
371 						}
372 					}
373 				}
374 			}
375 		}
376 
377 		if (commChannel == -1) {
378 			return null;
379 		}
380 
381 		/*
382 		 * build URL
383 		 */
384 		StringBuffer buf = new StringBuffer();
385 		if (isOBEX) {
386 			buf.append(BluetoothConsts.PROTOCOL_SCHEME_BT_OBEX);
387 		} else if (isRFCOMM) {
388 			buf.append(BluetoothConsts.PROTOCOL_SCHEME_RFCOMM);
389 		} else if (isL2CAP) {
390 			buf.append(BluetoothConsts.PROTOCOL_SCHEME_L2CAP);
391 		} else {
392 			return null;
393 		}
394 		buf.append("://");
395 
396 		if (device == null) {
397 			try {
398 				Object saveID = BlueCoveImpl.getCurrentThreadBluetoothStackID();
399 				try {
400 					BlueCoveImpl.setThreadBluetoothStack(bluetoothStack);
401 					buf.append(LocalDevice.getLocalDevice().getBluetoothAddress());
402 				} finally {
403 					if (saveID != null) {
404 						BlueCoveImpl.setThreadBluetoothStackID(saveID);
405 					}
406 				}
407 			} catch (BluetoothStateException bse) {
408 				DebugLog.error("can't read LocalAddress", bse);
409 				buf.append("localhost");
410 			}
411 		} else {
412 			buf.append(getHostDevice().getBluetoothAddress());
413 		}
414 
415 		buf.append(":");
416 		if (isL2CAP) {
417 			String hex = Integer.toHexString(commChannel);
418 			for (int i = hex.length(); i < 4; i++) {
419 				buf.append('0');
420 			}
421 			buf.append(hex);
422 		} else {
423 			buf.append(commChannel);
424 		}
425 
426 		switch (requiredSecurity) {
427 		case NOAUTHENTICATE_NOENCRYPT:
428 			buf.append(";authenticate=false;encrypt=false");
429 			break;
430 		case AUTHENTICATE_NOENCRYPT:
431 			buf.append(";authenticate=true;encrypt=false");
432 			break;
433 		case AUTHENTICATE_ENCRYPT:
434 			buf.append(";authenticate=true;encrypt=true");
435 			break;
436 		default:
437 			throw new IllegalArgumentException();
438 		}
439 
440 		if (mustBeMaster) {
441 			buf.append(";master=true");
442 		} else {
443 			buf.append(";master=false");
444 		}
445 
446 		return buf.toString();
447 	}
448 
449 	int getChannel(UUID protocolUUID) {
450 
451 		int channel = -1;
452 
453 		DataElement protocolDescriptor = getAttributeValue(BluetoothConsts.ProtocolDescriptorList);
454 		if ((protocolDescriptor == null) || (protocolDescriptor.getDataType() != DataElement.DATSEQ)) {
455 			return -1;
456 		}
457 
458 		/*
459 		 * get RFCOMM Channel or L2CAP PSM ProtocolDescriptorList is DATSEQ of
460 		 * DATSEQ of UUID and optional parameters
461 		 */
462 
463 		for (Enumeration protocolsSeqEnum = (Enumeration) protocolDescriptor.getValue(); protocolsSeqEnum
464 				.hasMoreElements();) {
465 			DataElement elementSeq = (DataElement) protocolsSeqEnum.nextElement();
466 
467 			if (elementSeq.getDataType() == DataElement.DATSEQ) {
468 				Enumeration elementSeqEnum = (Enumeration) elementSeq.getValue();
469 
470 				if (elementSeqEnum.hasMoreElements()) {
471 					DataElement protocolElement = (DataElement) elementSeqEnum.nextElement();
472 					if (protocolElement.getDataType() != DataElement.UUID) {
473 						continue;
474 					}
475 					Object uuid = protocolElement.getValue();
476 					if (elementSeqEnum.hasMoreElements() && (protocolUUID.equals(uuid))) {
477 
478 						DataElement protocolPSMElement = (DataElement) elementSeqEnum.nextElement();
479 
480 						switch (protocolPSMElement.getDataType()) {
481 						case DataElement.U_INT_1:
482 						case DataElement.U_INT_2:
483 						case DataElement.U_INT_4:
484 						case DataElement.INT_1:
485 						case DataElement.INT_2:
486 						case DataElement.INT_4:
487 						case DataElement.INT_8:
488 							channel = (int) protocolPSMElement.getLong();
489 							break;
490 						}
491 					}
492 				}
493 			}
494 		}
495 		return channel;
496 	}
497 
498 	/*
499 	 * Used by a server application to indicate the major service class bits
500 	 * that should be activated in the server's DeviceClass when this
501 	 * ServiceRecord is added to the SDDB. When client devices do device
502 	 * discovery, the server's DeviceClass is provided as one of the arguments
503 	 * of the deviceDiscovered method of the DiscoveryListener interface. Client
504 	 * devices can consult the DeviceClass of the server device to get a general
505 	 * idea of the kind of device this is (e.g., phone, PDA, or PC) and the
506 	 * major service classes it offers (e.g., rendering, telephony, or
507 	 * information). A server application should use the setDeviceServiceClasses
508 	 * method to describe its service in terms of the major service classes.
509 	 * This allows clients to obtain a DeviceClass for the server that
510 	 * accurately describes all of the services being offered. When
511 	 * acceptAndOpen() is invoked for the first time on the notifier associated
512 	 * with this ServiceRecord, the classes argument from the
513 	 * setDeviceServiceClasses method is OR'ed with the current setting of the
514 	 * major service class bits of the local device. The OR operation
515 	 * potentially activates additional bits. These bits may be retrieved by
516 	 * calling getDeviceClass() on the LocalDevice object. Likewise, a call to
517 	 * LocalDevice.updateRecord() will cause the major service class bits to be
518 	 * OR'ed with the current settings and updated.
519 	 *
520 	 * The documentation for DeviceClass gives examples of the integers that
521 	 * describe each of the major service classes and provides a URL for the
522 	 * complete list. These integers can be used individually or OR'ed together
523 	 * to describe the appropriate value for classes.
524 	 *
525 	 * Later, when this ServiceRecord is removed from the SDDB, the
526 	 * implementation will automatically deactivate the device bits that were
527 	 * activated as a result of the call to setDeviceServiceClasses. The only
528 	 * exception to this occurs if there is another ServiceRecord that is in the
529 	 * SDDB and setDeviceServiceClasses has been sent to that other
530 	 * ServiceRecord to request that some of the same bits be activated.
531 	 *
532 	 * Parameters: classes - an integer whose binary representation indicates
533 	 * the major service class bits that should be activated Throws:
534 	 * IllegalArgumentException - if classes is not an OR of one or more of the
535 	 * major service class integers in the Bluetooth Assigned Numbers document.
536 	 * While Limited Discoverable Mode is included in this list of major service
537 	 * classes, its bit is activated by placing the device in Limited
538 	 * Discoverable Mode (see the GAP specification), so if bit 13 is set this
539 	 * exception will be thrown. RuntimeExceptin - if the ServiceRecord
540 	 * receiving the message was obtained from a remote device
541 	 */
542 
543 	public void setDeviceServiceClasses(int classes) {
544 		if (device != null) {
545 			throw new RuntimeException("Service record obtained from a remote device");
546 		}
547 		if ((classes & (0xff000000 | DeviceClassConsts.LIMITED_DISCOVERY_SERVICE | DeviceClassConsts.FORMAT_VERSION_MASK)) != 0) {
548 			throw new IllegalArgumentException();
549 		}
550 		if ((classes & (DeviceClassConsts.MAJOR_MASK | DeviceClassConsts.MINOR_MASK)) != 0) {
551 			throw new IllegalArgumentException();
552 		}
553 
554 		if ((bluetoothStack.getFeatureSet() & BluetoothStack.FEATURE_SET_DEVICE_SERVICE_CLASSES) == 0) {
555 			throw new NotSupportedRuntimeException(bluetoothStack.getStackID());
556 		}
557 
558 		this.deviceServiceClasses = classes;
559 	}
560 
561 	/*
562 	 * Modifies this ServiceRecord to contain the service attribute defined by
563 	 * the attribute-value pair (attrID, attrValue). If the attrID does not
564 	 * exist in the ServiceRecord, this attribute-value pair is added to this
565 	 * ServiceRecord object. If the attrID is already in this ServiceRecord, the
566 	 * value of the attribute is changed to attrValue. If attrValue is null, the
567 	 * attribute with the attribute ID of attrID is removed from this
568 	 * ServiceRecord object. If attrValue is null and attrID does not exist in
569 	 * this object, this method will return false. This method makes no
570 	 * modifications to a service record in the SDDB. In order for any changes
571 	 * made by this method to be reflected in the SDDB, a call must be made to
572 	 * the acceptAndOpen() method of the associated notifier to add this
573 	 * ServiceRecord to the SDDB for the first time, or a call must be made to
574 	 * the updateRecord() method of LocalDevice to modify the version of this
575 	 * ServiceRecord that is already in the SDDB.
576 	 *
577 	 * This method prevents the ServiceRecordHandle from being modified by
578 	 * throwing an IllegalArgumentException.
579 	 *
580 	 * Parameters: attrID - the service attribute ID attrValue - the DataElement
581 	 * which is the value of the service attribute Returns: true if the service
582 	 * attribute was successfully added, removed, or modified; false if
583 	 * attrValue is null and attrID is not in this object Throws:
584 	 * IllegalArgumentException - if attrID does not represent a 16-bit unsigned
585 	 * integer; if attrID is the value of ServiceRecordHandle (0x0000)
586 	 * RuntimeException - if this method is called on a ServiceRecord that was
587 	 * created by a call to DiscoveryAgent.searchServices()
588 	 */
589 
590 	public boolean setAttributeValue(int attrID, DataElement attrValue) {
591 		/*
592 		 * check this is a local service record
593 		 */
594 
595 		if (device != null) {
596 			throw new IllegalArgumentException();
597 		}
598 
599 		if (attrID < 0x0000 || attrID > 0xffff) {
600 			throw new IllegalArgumentException();
601 		}
602 
603 		if (attrID == BluetoothConsts.ServiceRecordHandle) {
604 			throw new IllegalArgumentException();
605 		}
606 
607 		/*
608 		 * remove, add or modify attribute
609 		 */
610 
611 		attributeUpdated = true;
612 		if (attrValue == null) {
613 			return (attributes.remove(new Integer(attrID)) != null);
614 		} else {
615 			attributes.put(new Integer(attrID), attrValue);
616 			return true;
617 		}
618 	}
619 
620 	/**
621 	 * Internal implementation function
622 	 */
623 	void populateAttributeValue(int attrID, DataElement attrValue) {
624 		if (attrID < 0x0000 || attrID > 0xffff) {
625 			throw new IllegalArgumentException();
626 		}
627 		if (attrValue == null) {
628 			attributes.remove(new Integer(attrID));
629 		} else {
630 			attributes.put(new Integer(attrID), attrValue);
631 		}
632 	}
633 
634 	public String toString() {
635 
636 		StringBuffer buf = new StringBuffer("{\n");
637 
638 		for (Enumeration e = attributes.keys(); e.hasMoreElements();) {
639 			Integer i = (Integer) e.nextElement();
640 
641 			buf.append("0x");
642 			buf.append(Integer.toHexString(i.intValue()));
643 			buf.append(":\n\t");
644 
645 			DataElement d = (DataElement) attributes.get(i);
646 
647 			buf.append(d);
648 			buf.append("\n");
649 		}
650 
651 		buf.append("}");
652 
653 		return buf.toString();
654 	}
655 
656 	/**
657 	 * Internal implementation function
658 	 */
659 	long getHandle() {
660 		return this.handle;
661 	}
662 
663 	/**
664 	 * Internal implementation function
665 	 */
666 	void setHandle(long handle) {
667 		this.handle = handle;
668 	}
669 
670 	/**
671 	 * Internal implementation function
672 	 */
673 	boolean hasServiceClassUUID(UUID uuid) {
674 		DataElement attrDataElement = getAttributeValue(BluetoothConsts.ServiceClassIDList);
675 		if ((attrDataElement == null) || (attrDataElement.getDataType() != DataElement.DATSEQ)
676 				|| attrDataElement.getSize() == 0) {
677 			// DebugLog.debug("Bogus ServiceClassIDList");
678 			return false;
679 		}
680 
681 		Object value = attrDataElement.getValue();
682 		if ((value == null) || (!(value instanceof Enumeration))) {
683 			DebugLog.debug("Bogus Value in DATSEQ");
684 			if (value != null) {
685 				DebugLog.error("DATSEQ class " + value.getClass().getName());
686 			}
687 			return false;
688 		}
689 		for (Enumeration e = (Enumeration) value; e.hasMoreElements();) {
690 			Object element = e.nextElement();
691 			if (!(element instanceof DataElement)) {
692 				DebugLog.debug("Bogus element in DATSEQ, " + value.getClass().getName());
693 				continue;
694 			}
695 			DataElement dataElement = (DataElement) element;
696 			if ((dataElement.getDataType() == DataElement.UUID) && (uuid.equals(dataElement.getValue()))) {
697 				return true;
698 			}
699 		}
700 
701 		return false;
702 	}
703 
704 	boolean hasProtocolClassUUID(UUID uuid) {
705 		DataElement protocolDescriptor = getAttributeValue(BluetoothConsts.ProtocolDescriptorList);
706 		if ((protocolDescriptor == null) || (protocolDescriptor.getDataType() != DataElement.DATSEQ)) {
707 			// DebugLog.debug("Bogus ProtocolDescriptorList");
708 			return false;
709 		}
710 
711 		for (Enumeration protocolsSeqEnum = (Enumeration) protocolDescriptor.getValue(); protocolsSeqEnum
712 				.hasMoreElements();) {
713 			DataElement elementSeq = (DataElement) protocolsSeqEnum.nextElement();
714 
715 			if (elementSeq.getDataType() == DataElement.DATSEQ) {
716 				Enumeration elementSeqEnum = (Enumeration) elementSeq.getValue();
717 				if (elementSeqEnum.hasMoreElements()) {
718 					DataElement protocolElement = (DataElement) elementSeqEnum.nextElement();
719 					if (protocolElement.getDataType() != DataElement.UUID) {
720 						continue;
721 					}
722 					if (uuid.equals(protocolElement.getValue())) {
723 						return true;
724 					}
725 				}
726 			}
727 		}
728 		return false;
729 	}
730 
731 	DataElement clone(DataElement de) {
732 		DataElement c = null;
733 
734 		switch (de.getDataType()) {
735 		case DataElement.U_INT_1:
736 		case DataElement.U_INT_2:
737 		case DataElement.U_INT_4:
738 		case DataElement.INT_1:
739 		case DataElement.INT_2:
740 		case DataElement.INT_4:
741 			c = new DataElement(de.getDataType(), de.getLong());
742 			break;
743 		case DataElement.URL:
744 		case DataElement.STRING:
745 		case DataElement.UUID:
746 		case DataElement.INT_16:
747 		case DataElement.INT_8:
748 		case DataElement.U_INT_16:
749 			c = new DataElement(de.getDataType(), de.getValue());
750 			break;
751 		case DataElement.NULL:
752 			c = new DataElement(de.getDataType());
753 			break;
754 		case DataElement.BOOL:
755 			c = new DataElement(de.getBoolean());
756 			break;
757 		case DataElement.DATSEQ:
758 		case DataElement.DATALT:
759 			c = new DataElement(de.getDataType());
760 			for (Enumeration en = (Enumeration) de.getValue(); en.hasMoreElements();) {
761 				DataElement dataElement = (DataElement) en.nextElement();
762 				c.addElement(clone(dataElement));
763 			}
764 		}
765 
766 		return c;
767 	}
768 
769 	/**
770 	 * Internal implementation function
771 	 */
772 	void populateRFCOMMAttributes(long handle, int channel, UUID uuid, String name, boolean obex) {
773 
774 		this.populateAttributeValue(BluetoothConsts.ServiceRecordHandle, new DataElement(DataElement.U_INT_4, handle));
775 
776 		/*
777 		 * service class ID list
778 		 */
779 
780 		DataElement serviceClassIDList = new DataElement(DataElement.DATSEQ);
781 		serviceClassIDList.addElement(new DataElement(DataElement.UUID, uuid));
782 		if (!obex) {
783 			serviceClassIDList.addElement(new DataElement(DataElement.UUID, BluetoothConsts.SERIAL_PORT_UUID));
784 		}
785 
786 		this.populateAttributeValue(BluetoothConsts.ServiceClassIDList, serviceClassIDList);
787 
788 		/*
789 		 * protocol descriptor list
790 		 */
791 
792 		DataElement protocolDescriptorList = new DataElement(DataElement.DATSEQ);
793 
794 		DataElement L2CAPDescriptor = new DataElement(DataElement.DATSEQ);
795 		L2CAPDescriptor.addElement(new DataElement(DataElement.UUID, BluetoothConsts.L2CAP_PROTOCOL_UUID));
796 		protocolDescriptorList.addElement(L2CAPDescriptor);
797 
798 		DataElement RFCOMMDescriptor = new DataElement(DataElement.DATSEQ);
799 		RFCOMMDescriptor.addElement(new DataElement(DataElement.UUID, BluetoothConsts.RFCOMM_PROTOCOL_UUID));
800 		RFCOMMDescriptor.addElement(new DataElement(DataElement.U_INT_1, channel));
801 		protocolDescriptorList.addElement(RFCOMMDescriptor);
802 
803 		if (obex) {
804 			DataElement OBEXDescriptor = new DataElement(DataElement.DATSEQ);
805 			OBEXDescriptor.addElement(new DataElement(DataElement.UUID, BluetoothConsts.OBEX_PROTOCOL_UUID));
806 			protocolDescriptorList.addElement(OBEXDescriptor);
807 		}
808 
809 		this.populateAttributeValue(BluetoothConsts.ProtocolDescriptorList, protocolDescriptorList);
810 
811 		if (name != null) {
812 			this.populateAttributeValue(BluetoothConsts.AttributeIDServiceName, new DataElement(DataElement.STRING,
813 					name));
814 		}
815 	}
816 
817 	void populateL2CAPAttributes(int handle, int channel, UUID uuid, String name) {
818 
819 		this.populateAttributeValue(BluetoothConsts.ServiceRecordHandle, new DataElement(DataElement.U_INT_4, handle));
820 
821 		/*
822 		 * service class ID list
823 		 */
824 
825 		DataElement serviceClassIDList = new DataElement(DataElement.DATSEQ);
826 		serviceClassIDList.addElement(new DataElement(DataElement.UUID, uuid));
827 
828 		this.populateAttributeValue(BluetoothConsts.ServiceClassIDList, serviceClassIDList);
829 
830 		/*
831 		 * protocol descriptor list
832 		 */
833 		DataElement protocolDescriptorList = new DataElement(DataElement.DATSEQ);
834 
835 		DataElement L2CAPDescriptor = new DataElement(DataElement.DATSEQ);
836 		L2CAPDescriptor.addElement(new DataElement(DataElement.UUID, BluetoothConsts.L2CAP_PROTOCOL_UUID));
837 		L2CAPDescriptor.addElement(new DataElement(DataElement.U_INT_2, channel));
838 		protocolDescriptorList.addElement(L2CAPDescriptor);
839 
840 		this.populateAttributeValue(BluetoothConsts.ProtocolDescriptorList, protocolDescriptorList);
841 
842 		if (name != null) {
843 			this.populateAttributeValue(BluetoothConsts.AttributeIDServiceName, new DataElement(DataElement.STRING,
844 					name));
845 		}
846 	}
847 }