commands/btservices/BtServicesEng.cpp
changeset 0 7f656887cf89
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/commands/btservices/BtServicesEng.cpp	Wed Jun 23 15:52:26 2010 +0100
@@ -0,0 +1,989 @@
+// BtServicesEng.cpp
+// 
+// Copyright (c) 2003 - 2010 Accenture. All rights reserved.
+// This component and the accompanying materials are made available
+// under the terms of the "Eclipse Public License v1.0"
+// which accompanies this distribution, and is available
+// at the URL "http://www.eclipse.org/legal/epl-v10.html".
+// 
+// Initial Contributors:
+// Accenture - Initial contribution
+//
+
+//#include <qbtselectdlg.h>
+
+#include <e32debug.h>
+
+#include <utf.h>
+#include "BtData.h"
+#include "btserviceseng.h"
+#include <btmanclient.h>
+//#include <MQikListBoxData.h>
+
+const TInt KArrayIncrement = 5;
+const TInt KMaxServiceNameLength = 256;
+const TInt KDefaultLanguage = 0x0100;
+const TInt KIndentationFactor = 3;
+
+_LIT(KTextHexPrefix,"0x");
+
+CBtServicesEng::CBtServicesEng()
+	{
+	iSdpAgent = NULL;
+	iSearchPattern = NULL;
+	iAttributeArray = NULL;
+	
+	iView = NULL;
+	
+	iAttributeMatchList = NULL;
+	iAttributeBuilder = NULL;
+	
+	iUUIDFilter = (KL2CAP);
+	}
+
+CBtServicesEng::~CBtServicesEng()
+	{
+	delete iSdpAgent; // no need to Cancel separately
+	delete iSearchPattern;
+	if (iAttributeArray)
+		{
+		iAttributeArray->Reset();
+		delete iAttributeArray;
+		}
+	iRecordArray.ResetAndDestroy();
+	delete iAttributeMatchList;
+	delete iAttributeBuilder;
+
+	iSocketServ.Close();
+	iUUIDs.Close();
+	iUUIDsAsText.ResetAndDestroy();
+	iUUIDsAsText.Close();
+
+	iErrorCodes.Close();
+	iErrorMessages.ResetAndDestroy();
+	iErrorMessages.Close();
+	}
+
+CBtServicesEng* CBtServicesEng::NewL()
+	{
+	CBtServicesEng* self = new(ELeave) CBtServicesEng();
+	CleanupStack::PushL(self);
+	self->ConstructL();
+	CleanupStack::Pop(self);
+	return self;
+	}
+
+void CBtServicesEng::ConstructL()
+	{
+	//iEikon = CEikonEnv::Static();
+
+	iSearchPattern = CSdpSearchPattern::NewL();
+	iAttributeArray = new(ELeave) CDesCArrayFlat(KArrayIncrement);
+	iAttributeBuilder = CBtAttributeBuilder::NewL(*this);
+
+	//read the available filter UUID and corresponding text
+	TInt arrayCount = sizeof(UuidAsArray) / sizeof(TUUidPair);
+	RDebug::Printf("CBtServicesEng::ConstructL() arrayCount= %d \r\n", 
+			arrayCount);
+	
+	TInt i;
+	for (i = 0; i < arrayCount; i++)
+		{
+		TUint32 uuid = UuidAsArray[i].uuid;
+		iUUIDs.Append(TUUID(uuid));
+		
+		HBufC* textForm = HBufC::NewL(64);
+		RDebug::Printf("i=%d HBufC* textForm = 0x%08x \r\n", 
+				i, textForm);
+
+		RDebug::Printf("i=%d HBufC* textForm = 0x%08x \r\n", 
+				i, textForm);
+	
+		TPtrC UuidText = UuidAsArray[i].txt;
+		RDebug::Print(_L("UuidText: %S \r\n"), &UuidText);
+
+		*textForm = UuidText;
+				
+		iUUIDsAsText.Append(textForm);
+		}
+
+	//==================================================
+	/*
+	buf = iEikon->AllocReadResourceAsDes8LC(R_BLUETOOTH_ERROR_MESSAGES);
+	res.SetBuffer(buf);
+	arrayCount = res.ReadInt16();
+	for (i = 0; i < arrayCount; i++)
+		{
+		TInt errorCode = res.ReadInt32();
+		iErrorCodes.Append(errorCode);
+		HBufC* message = res.ReadHBufCL();
+		iErrorMessages.Append(message);
+		}
+	CleanupStack::PopAndDestroy(); // buf
+	*/
+
+	User::LeaveIfError(iSocketServ.Connect());
+	}
+
+void CBtServicesEng::ReportMessage(TInt aVerboseLevel, TRefByValue<const TDesC> aFmt, ...)
+	{
+	if (iView)
+		{
+		VA_LIST list;
+		VA_START(list, aFmt);
+		iTextBuf.FormatList(aFmt, list);
+		iView->PrintTextMsg(aVerboseLevel, iTextBuf);
+		}
+	}
+
+//generate a text list of UUID value/string
+void CBtServicesEng::MakeTextUUIDList()
+	{
+	TInt arrayCount = sizeof(UuidAsArray) / sizeof(TUUidPair);
+	TInt i;
+	ReportMessage(0, _L("========================\r\n"));	
+	ReportMessage(0, _L("UUID Value | UUID STRING\r\n"));	
+	ReportMessage(0, _L("========================\r\n"));	
+	for (i = 0; i < arrayCount; i++)
+		{
+		TUint32 uuid = UuidAsArray[i].uuid;
+		TPtrC string = UuidAsArray[i].txt;
+		
+		ReportMessage(0, _L("0x%08x | %S\r\n"), uuid, &string);
+		}
+	}
+
+//This function should only be called after NewDeviceSelectedL() asynchrnously completes; 
+//generate a text list of services found (handle and name)
+//
+void CBtServicesEng::MakeTextServiceList()
+	{
+	TInt i;
+	
+	TPtrC UuidStr = UUIDToString(iUUIDFilter);
+	ReportMessage(0, _L("UUID filter 0x%08x %S\r\n"), iUUIDFilter, &UuidStr);			
+	
+	TInt HandlesCnt = iRecordArray.Count();
+	
+	if (HandlesCnt)
+		{
+		ReportMessage(0, _L("============================\r\n"));		
+		ReportMessage(0, _L("ServiceHandle | Service Name\r\n"));		
+		ReportMessage(0, _L("============================\r\n"));		
+		}
+	
+	for (i=0; i<HandlesCnt; i++)
+		{
+		TSdpServRecordHandle Handle = iRecordArray[i]->iHandle;
+		const TDesC& Name = iRecordArray[i]->iServiceName;
+		
+		ReportMessage(0, _L("0x%08x    | \"%S\"\r\n"), Handle, &Name);
+		
+		}
+		
+	}
+
+//
+void CBtServicesEng::NewDeviceSelectedL(const TBTDevAddr& aAddress,const TBTDeviceName& aDeviceName)
+	{
+	iDeviceName = aDeviceName;
+	iDeviceAddress = aAddress;
+
+	GetDeviceServicesL();
+	}
+
+/**
+This starts the SDP agent searching for services.
+Firstly, the previous (if any) SDP agent is deleted and a
+new one constructed with the address of the new device. The
+record filter is set based on the UUID filter settings.
+The NextRecordRequestL() function begins the search, with the
+results being received via the MSdpAgentNotifier functions.
+*/
+void CBtServicesEng::GetDeviceServicesL()
+	{
+	if (iSdpAgent)
+		{
+		DeleteSdpAgent();
+		}
+	iSdpAgent = CSdpAgent::NewL(*this, iDeviceAddress); // create new SDP Agent for given device address
+
+	iSearchPattern->Reset();
+	iSearchPattern->AddL(iUUIDFilter);
+	iSdpAgent->SetRecordFilterL(*iSearchPattern);
+
+	iRecordArray.ResetAndDestroy();
+//	MQikListBoxModel& model = *this;
+//	model.RemoveAllDataL();
+	//iView->ListBox().HandleItemRemovalL();
+	//iView->ListBox().DrawNow();
+
+	/*
+	TGulAlignment position(EHRightVCenter);
+	_LIT(KMsgFindingServices, "Finding services");
+	iView->GetEikonEnv()->BusyMsgL(KMsgFindingServices, position);
+	iView->DimCancelButton(EFalse);
+	iView->DimRefreshButton(ETrue);
+	 */
+	
+	iSdpAgent->NextRecordRequestL();
+	}
+
+/**
+The Sdp agent initially returns only record handles for each
+service - this function then gets the name attribute for
+each one.
+*/
+void CBtServicesEng::GetServiceNamesL()
+	{
+	if (iHandleCounter < iRecordArray.Count())
+		{
+		TSdpAttributeID attributeID = KSdpAttrIdOffsetServiceName + KDefaultLanguage; // only support default language for SDP
+		iSdpAgent->AttributeRequestL(iRecordArray[iHandleCounter]->iHandle, 
+				attributeID);
+				
+		ReportMessage(1, _L("Query service name for handle 0x%X...\r\n"), 
+				iRecordArray[iHandleCounter]->iHandle );
+		}
+	else
+		{
+		iCurrentAttributeRequest = ENoOutstandingRequests;		
+//		iView->GetEikonEnv()->BusyMsgCancel();
+//		iView->DimCancelButton(ETrue);
+//		iView->DimRefreshButton(EFalse);
+		
+		if (iView)
+			iView->AsyncCompleted();
+		}
+	}
+
+/**
+Begins getting the full attribute list
+for a given service. Because an MSdpElementBuilder
+pointer is passed in AttributeRequestL(), the SDP agent
+calls the builder functions with each result, rather than
+AttributeRequestResult(). 
+*/
+void CBtServicesEng::GetAttributesL(TInt aIndex)
+	{
+	iCurrentAttributeRequest = EGettingAllAttributes;
+	iServiceName.Set(iRecordArray[aIndex]->iServiceName);
+	iAttributeArray->Reset();
+	iAttributeBuilder->Reset();
+	if (iAttributeMatchList)
+		{
+		delete iAttributeMatchList;
+		iAttributeMatchList = NULL;
+		}
+	iAttributeMatchList = CSdpAttrIdMatchList::NewL();
+	iAttributeMatchList->AddL(TAttrRange(0, KMaxTUint16)); // getting all attributes
+	iAttributeBuilder->SetHandle(iRecordArray[aIndex]->iHandle);
+	iSdpAgent->AttributeRequestL(iAttributeBuilder, iRecordArray[aIndex]->iHandle, *iAttributeMatchList);
+	}
+
+/**
+this function is supposed to be called 
+WITHOUT calling "NewDeviceSelectedL()"
+
+Begins getting the full attribute list
+for a given service. Because an MSdpElementBuilder
+pointer is passed in AttributeRequestL(), the SDP agent
+calls the builder functions with each result, rather than
+AttributeRequestResult(). 
+*/
+void CBtServicesEng::GetDeviceAttributesL(const TBTDevAddr& aAddress,const TBTDeviceName& aDeviceName, TSdpServRecordHandle aHandle)
+	{
+	iDeviceName = aDeviceName;
+	iDeviceAddress = aAddress;
+	if (iSdpAgent)
+		{
+		DeleteSdpAgent();
+		}
+	iSdpAgent = CSdpAgent::NewL(*this, iDeviceAddress); // create new SDP Agent for given device address
+		
+	iCurrentAttributeRequest = EGettingAllAttributes;
+	//iServiceName.Set((*iTextArray)[aIndex]);
+	iAttributeArray->Reset();
+	iAttributeBuilder->Reset();
+	if (iAttributeMatchList)
+		{
+		delete iAttributeMatchList;
+		iAttributeMatchList = NULL;
+		}
+	iAttributeMatchList = CSdpAttrIdMatchList::NewL();
+	iAttributeMatchList->AddL(TAttrRange(0, KMaxTUint16)); // getting all attributes
+	iAttributeBuilder->SetHandle(aHandle);
+	iSdpAgent->AttributeRequestL(iAttributeBuilder, aHandle, *iAttributeMatchList);
+	}
+
+
+void CBtServicesEng::CancelSdpAgent()
+	{
+	if (iSdpAgent)
+		{
+		iSdpAgent->Cancel();
+		}
+	}
+
+void CBtServicesEng::DeleteSdpAgent()
+	{
+	delete iSdpAgent;
+	iSdpAgent = NULL;
+	}
+
+void CBtServicesEng::SetUUIDFilterL(TUint32 aUUID)
+	{
+	iUUIDFilter = aUUID;
+	}
+
+void CBtServicesEng::AppendUUIDText(TDes& aBuf, TUUID aUUID) const
+	{
+	if (aUUID == TUUID(0))
+		{
+		aUUID = TUUID(iUUIDFilter);
+		}
+	TInt i = 0;
+	TInt UuidCount = iUUIDs.Count();
+	for (i = 0; i < UuidCount; i++)
+		{
+		if (iUUIDs[i] == aUUID)
+			{
+			aBuf.Append(*(iUUIDsAsText[i]));
+			return;
+			}
+		}
+	_LIT(KTextUUIDHex,"UUID: 0x");
+	aBuf.Append(KTextUUIDHex);
+	
+	const TPtrC8 ptr = aUUID.ShortestForm();
+	TInt ShortestLength = ptr.Length();
+	for (i = 0; i<ShortestLength ; i++) // get shortest form
+		{
+		aBuf.AppendNumFixedWidth(aUUID[i],EHex,2);
+		}
+	}
+
+void CBtServicesEng::DisplayDeviceInfoL()
+	{
+//	iView->DisplayDeviceInfoDialogL(iDeviceName,iDeviceAddress,iDeviceClass);
+	}
+
+//display local BT address, device name and class info
+//
+void CBtServicesEng::DisplayLocalInfoL()
+	{
+	/*
+	RSocket l2CapSock;
+	TInt err = l2CapSock.Open(iSocketServ, KBTAddrFamily, KSockSeqPacket, KL2CAP);
+	if (err != KErrNone)
+		{
+		_LIT(KErrorText,"Not supported");
+		TGulAlignment position(EHRightVTop);
+		iView->GetEikonEnv()->InfoMsgWithAlignmentAndDuration(position,KErrorText,KErrorMsgDuration);
+		return;
+		}
+	CleanupClosePushL(l2CapSock);
+	TRequestStatus stat;
+	
+	TBTDevAddrPckg addrbuf;
+	l2CapSock.Ioctl(KHCILocalAddressIoctl, stat, &addrbuf, KSolBtHCI); // takes a TBTDevAddrPckg (TPckgBuf<TBTDevAddr>) as documented in 7.0 SDK
+	User::WaitForRequest(stat);
+	User::LeaveIfError(stat.Int());
+
+	THCIDeviceClassBuf classbuf;
+	l2CapSock.Ioctl(KHCIReadDeviceClassIoctl, stat, &classbuf, KSolBtHCI); // takes a THCIDeviceClassBuf (TPckgBuf<THCIDeviceClassIoctl>) as documented in 7.0 SDK
+	User::WaitForRequest(stat);
+	User::LeaveIfError(stat.Int());
+	CleanupStack::PopAndDestroy(); // l2CapSock
+
+	RHostResolver hr;
+	User::LeaveIfError(hr.Open(iSocketServ, KBTAddrFamily, KBTLinkManager));
+	CleanupClosePushL(hr);
+	TBTDeviceName deviceName;
+	User::LeaveIfError(hr.GetHostName(deviceName));
+	CleanupStack::PopAndDestroy();
+
+	TBTDevAddr& deviceAddress = addrbuf();
+	TBTDeviceClass deviceClass(classbuf().iMajorServiceClass, classbuf().iMajorDeviceClass,classbuf().iMinorDeviceClass);
+	*/
+
+	RBTRegServ serv;
+	RBTLocalDevice me;
+	User::LeaveIfError(serv.Connect());
+	CleanupClosePushL(serv);
+	User::LeaveIfError(me.Open(serv));
+	CleanupClosePushL(me);
+	TBTLocalDevice dev;
+	User::LeaveIfError(me.Get(dev));
+
+	TBTDeviceName deviceName;
+	deviceName.Copy(dev.DeviceName().Left(deviceName.MaxLength()));
+	TBTDeviceClass deviceClass(dev.DeviceClass());
+	// RBtLocalDevice doesn't seem to return the address
+	//const TBTDevAddr& deviceAddress = dev.Address();
+	TBTDevAddr deviceAddress;
+	TPtr8 ptr(deviceAddress.Des());
+	User::LeaveIfError(RProperty::Get(KPropertyUidBluetoothCategory, KPropertyKeyBluetoothGetLocalDeviceAddress, ptr));
+
+	CleanupStack::PopAndDestroy(2, &serv);
+	
+	TBuf<64> ReadableAddr;
+	deviceAddress.GetReadable(ReadableAddr);
+	ReportMessage(0, _L("BTAddress:0x%S, Name:\"%S\", DeviceClass=0x%08x\r\n"), 
+			&ReadableAddr, &deviceName, deviceClass.DeviceClass());
+
+	/*
+#if 0 //test code, not used	
+	//query further about the services provided by this device
+	RSdp sdp;
+	sdp.Connect();
+	CleanupClosePushL(sdp);
+		
+	RSdpDatabase sdpData;
+	sdpData.Open(sdp);
+	CleanupClosePushL(sdpData);				
+
+	CleanupStack::PopAndDestroy();
+	CleanupStack::PopAndDestroy();
+#endif	
+	*/
+	}
+
+
+void CBtServicesEng::SetView(CBtServiceView* apView)
+	{
+	iView = apView;
+	}
+	
+
+/*
+CDesCArrayFlat* CBtServicesEng::GetTextArray() const
+	{
+	return iTextArray;
+	}
+*/
+
+/**
+Checks whether bluetooth is enabled by opening a host
+resolver and immediately closing it again.
+*/
+TInt CBtServicesEng::BluetoothReady()
+	{
+	RHostResolver hr;
+	TInt err = hr.Open(iSocketServ, KBTAddrFamily, KBTLinkManager); // just to check if bluetooth is enabled
+	hr.Close();
+	return err;
+	}
+
+TBool CBtServicesEng::GettingAllAttributes() const
+	{
+	return (iCurrentAttributeRequest == EGettingAllAttributes);
+	}
+
+void CBtServicesEng::AddAttributeLineL(const TDesC& aDes)
+	{
+	iAttributeArray->AppendL(aDes);
+	}
+
+void CBtServicesEng::AttributeRequestComplete(TSdpServRecordHandle  aHandle, TInt aError)
+	{
+	ReportMessage(1, _L("[AttributeRequestComplete for Handle:0x%X Err:%d]\r\n\r\n"), aHandle, aError);
+	
+	if(aError == KErrNone)
+		{
+		TRAP(aError, DoAttributeRequestCompleteL());
+		}
+	if(aError != KErrNone)
+		{
+		SdpCompletedWithError(aError);
+		}
+	}
+
+void CBtServicesEng::DoAttributeRequestCompleteL()
+	{
+	if (iCurrentAttributeRequest == EGettingNamesOnly)
+		{
+		//there are a lot of handles that needs the name, so 
+		//need to keep issue requests until all handles are processed,
+		//so the whole asynchronous process is not finished yet
+		iHandleCounter++;
+		GetServiceNamesL();
+		}
+	else if (iCurrentAttributeRequest == EGettingAllAttributes)
+		{ // all attributes have been got
+		TInt LineCount = iAttributeArray->Count();
+		
+		for (TInt i=0; i<LineCount; i++)
+			{
+			const TDesC& LineString = (*iAttributeArray)[i];						
+			ReportMessage(0, _L("%S\r\n"), &LineString);
+			}
+		
+//		iView->DisplayAttributeDialogL(iAttributeArray, iDeviceName, iServiceName);
+		iCurrentAttributeRequest = ENoOutstandingRequests;
+		if (iView)
+			iView->AsyncCompleted();
+		}
+	}
+
+void CBtServicesEng::AttributeRequestResult(TSdpServRecordHandle aHandle, TSdpAttributeID /*aAttrID*/, CSdpAttrValue* aAttrValue)
+	{
+	ReportMessage(1, _L("[AttributeRequestResult for Handle:0x%X ]\r\n"), aHandle );
+
+	TRAPD(err,DoAttributeRequestResultL(aAttrValue, aHandle)); // passing ownership
+	if (err != KErrNone)
+		{
+		iSdpAgent->Cancel();
+		}
+	}
+	
+void CBtServicesEng::DoAttributeRequestResultL(CSdpAttrValue* aAttrValue, TSdpServRecordHandle aHandle)
+	{
+	CleanupStack::PushL(aAttrValue); // taken ownership
+	if (iCurrentAttributeRequest == EGettingNamesOnly) 
+		{		 
+		TBuf16<KMaxServiceNameLength> unicode;
+		CnvUtfConverter::ConvertToUnicodeFromUtf8(unicode,aAttrValue->Des());
+		if (unicode[unicode.Length() - 1] == L'\0')
+			{
+			unicode.Delete(unicode.Length() - 1, 1); // remove NULL-termination
+			}
+		//iView->ListBox().HandleItemAdditionL();
+		TBuf<64> string;
+		string.Copy(unicode);
+		
+		//extra check to make sure Handle and Name are paired correctly
+		if (iRecordArray[iHandleCounter]->iHandle != aHandle)
+			User::Panic(_L("Handle not match"), KErrNone);
+		
+		iRecordArray[iHandleCounter]->iServiceName = string;
+		
+		ReportMessage(1, _L("%S \r\n"), &string);		
+/*		
+		MQikListBoxModel& model = *this;
+		model.ModelBeginUpdateLC();
+		MQikListBoxData* data = model.NewDataL(EDataNormal);
+		data->AddTextL(unicode, EQikListBoxSlotText1);
+		data->Close();
+		model.ModelEndUpdateL();
+*/		
+		}
+
+	CleanupStack::PopAndDestroy(aAttrValue);  
+	}
+
+void CBtServicesEng::NextRecordRequestComplete(TInt aError, TSdpServRecordHandle aHandle, TInt /* aTotalRecordsCount */)
+	{
+	if ((aError == KErrNone) || (aError == KErrEof))
+		{
+		TRAPD(err, DoNextRecordRequestCompleteL(aHandle, aError));
+		if (err != KErrNone)
+			{
+			aError = err;
+			}
+		}
+	if (aError != KErrNone && aError != KErrEof)
+		{
+//		iView->GetEikonEnv()->BusyMsgCancel();
+		SdpCompletedWithError(aError);
+		}
+	
+	//if aError == KErrEof, that means no more records to read
+	
+	}
+
+void CBtServicesEng::DoNextRecordRequestCompleteL(TSdpServRecordHandle aHandle, TInt aError)
+	{
+	if (aError == KErrNone)    
+		{
+		ReportMessage(1, _L("Record Handle found 0x%X \r\n"), aHandle );
+		
+		CServiceRecordItem* pItem = new CServiceRecordItem;
+		pItem->iHandle = aHandle;		
+		iRecordArray.Append(pItem);
+		iSdpAgent->NextRecordRequestL();
+		}
+	else if (aError == KErrEof)
+		//no more records, now query service name for each record found 
+		{
+		iHandleCounter = 0;
+		iCurrentAttributeRequest = EGettingNamesOnly;
+		
+		//report status
+		TInt RecordCnt = iRecordArray.Count();		
+		ReportMessage(1, _L("%d records found, querying service name...\r\n"), RecordCnt);
+		GetServiceNamesL();
+		}
+	}
+
+void CBtServicesEng::SdpCompletedWithError(const TInt aError)
+	{
+	
+	HBufC* buf = HBufC::NewLC(KMaxDescriptorLength);
+	TPtr errorText = buf->Des();
+	AppendErrorMessage(aError,errorText);
+//	TGulAlignment position(EHRightVTop);
+//	iView->GetEikonEnv()->InfoMsgWithAlignmentAndDuration(position,errorText,KErrorMsgDuration);
+	ReportMessage(0, *buf);
+	
+	CleanupStack::PopAndDestroy();
+	iSdpAgent->Cancel();
+//	iView->DimRefreshButton(EFalse);
+//	iView->DimCancelButton(ETrue);
+	if (iView)
+		iView->AsyncCompleted();
+	
+	}
+
+void CBtServicesEng::AppendErrorMessage(const TInt aError, TDes& aBuf) const
+	{
+	for (TInt i = 0; i < iErrorCodes.Count(); i++)
+		{
+		if (iErrorCodes[i] == aError)
+			{
+			aBuf.Append(*(iErrorMessages[i]));
+			return;
+			}
+		}
+	_LIT(KErrorTextOther, "Error: ");
+	aBuf.Append(KErrorTextOther);
+	aBuf.AppendNum(aError);
+	}
+
+void CBtServicesEng::Panic(TBtPanicCode aPanicCode)
+	{
+	_LIT(KBtPanicText, "BTSU::Panic ");
+	User::Panic(KBtPanicText, aPanicCode);
+	}
+
+void CBtServicesEng::ReleaseModel()
+	{
+	// Do nothing, list box does not own us
+	}
+
+//static
+//search the Uuid value, and return a text string of its name
+TPtrC CBtServicesEng::UUIDToString(TUint32 aUuidValue)
+	{
+	//read the available filter UUID and corresponding text
+	TInt arrayCount = sizeof(UuidAsArray) / sizeof(TUUidPair);
+	TInt i;
+	for (i = 0; i < arrayCount; i++)
+		{
+		TUint32 uuid = UuidAsArray[i].uuid;
+		if (uuid == aUuidValue)
+			return (UuidAsArray[i].txt);
+		}
+	return _L("");
+	}
+
+//static
+//search through the value, and return the UUID value if there is one match
+//aUuidValue: [out] the found result
+TBool CBtServicesEng::StringToUUID(TDesC& aString, TUint32& aUuidValue)
+	{
+	//read the available filter UUID and corresponding text
+	TInt arrayCount = sizeof(UuidAsArray) / sizeof(TUUidPair);
+	TInt i;
+	for (i = 0; i < arrayCount; i++)
+		{
+		TUint32 uuid = UuidAsArray[i].uuid;
+		if (aString == UuidAsArray[i].txt)
+			{
+			aUuidValue = uuid;
+			return ETrue;
+			}			
+		}
+	return EFalse;
+	}
+
+
+///////////////////////////////////////
+// CBtAttributeBuilder functions
+///////////////////////////////////////
+
+CBtAttributeBuilder* CBtAttributeBuilder::NewL(CBtServicesEng& aEngine)
+	{
+	CBtAttributeBuilder* self = new(ELeave) CBtAttributeBuilder(aEngine);
+	CleanupStack::PushL(self);
+	self->ConstructL();
+	CleanupStack::Pop(self);
+	return self;
+	}
+
+CBtAttributeBuilder::CBtAttributeBuilder(CBtServicesEng& aEngine)
+: iEngine(aEngine)
+	{
+	}
+
+void CBtAttributeBuilder::ConstructL()
+	{
+/*	
+	HBufC8* buf = CEikonEnv::Static()->AllocReadResourceAsDes8LC(R_ATTR_ID_MNEMONICS);
+
+	TResourceReader res;
+	res.SetBuffer(buf);
+
+	TInt arrayCount = res.ReadInt16();
+	for (TInt i = 0; i < arrayCount; i++)
+		{
+		TUint attrID = res.ReadUint32();
+		iAttrIDs.Append(attrID);
+		HBufC* mnemonic = res.ReadHBufCL();
+		iAttrMnemonics.Append(mnemonic);
+		}
+	CleanupStack::PopAndDestroy(); // buf
+*/	
+	}
+
+CBtAttributeBuilder::~CBtAttributeBuilder()
+	{
+	iAttrIDs.Close();
+	iAttrMnemonics.ResetAndDestroy();
+	iAttrMnemonics.Close();
+	}
+
+void CBtAttributeBuilder::Reset()
+	{
+	iIndentation = 0;
+	iCurrentLine.Zero();
+	}
+
+void CBtAttributeBuilder::SetHandle(TSdpServRecordHandle aHandle)
+	{
+	iHandle = aHandle;
+	}
+
+MSdpElementBuilder* CBtAttributeBuilder::BuildUnknownL(TUint8 aType, TUint8 /* aSizeDesc */, const TDesC8& /* aData */)
+	{
+	if (iStatus == EExpectingAttrID)
+		{
+		_LIT(KErrorText,"**Error: Unexpected UNKNOWN");
+		iCurrentLine.Append(KErrorText);
+		}
+	else
+		{
+		_LIT(KTextUnknown,"UNKNOWN");
+		iCurrentLine.Append(KTextUnknown);
+		iCurrentLine.AppendNum((TInt) aType);
+		}		
+	LineFinishedL();
+	return this;
+	}
+
+MSdpElementBuilder* CBtAttributeBuilder::BuildNilL()
+	{
+	if (iStatus == EExpectingAttrID)
+		{
+		_LIT(KErrorText,"**Error: Unexpected NIL");
+		iCurrentLine.Append(KErrorText);
+		}
+	else
+		{
+		_LIT(KTextNULL,"NULL");
+		iCurrentLine.Append(KTextNULL);
+		}
+	LineFinishedL();
+	return this;
+	}
+
+MSdpElementBuilder* CBtAttributeBuilder::BuildUintL(const TDesC8& aUint)
+	{
+
+	if (iStatus == EExpectingAttrValue)
+		{
+		iCurrentLine.Append(KTextHexPrefix);
+		for (TInt i = 0; i < aUint.Length(); i++)
+			{
+			iCurrentLine.AppendNumFixedWidth(aUint[i], EHex,2);
+			}
+		LineFinishedL();
+		}
+	else if (iStatus == EExpectingAttrID)
+		{
+		TUint value = 0;
+		for (TInt i = 0; i < aUint.Length(); i++)
+			{
+			value <<= 8;
+			value += aUint[i];
+			}
+		AppendAttrMnemonic(iCurrentLine,value);
+		iStatus = EExpectingAttrValue;
+		}
+
+	return this;
+	}
+
+MSdpElementBuilder* CBtAttributeBuilder::BuildIntL(const TDesC8& aInt)
+	{
+	if (iStatus == EExpectingAttrID)
+		{
+		_LIT(KErrorText,"**Error: Unexpected Int");
+		iCurrentLine.Append(KErrorText);
+		}
+	else
+		{
+		iCurrentLine.Append(KTextHexPrefix);
+		for (TInt i = 0; i < aInt.Length(); i++)
+			{
+			iCurrentLine.AppendNumFixedWidth(aInt[i], EHex,2);
+			}
+		}
+	LineFinishedL();
+	return this;
+	}
+
+MSdpElementBuilder* CBtAttributeBuilder::BuildUUIDL(const TUUID& aUUID)
+	{
+	if (iStatus == EExpectingAttrID)
+		{
+		_LIT(KErrorText,"**Error: Unexpected UUID");
+		iCurrentLine.Append(KErrorText);
+		}
+	else
+		{
+		iEngine.AppendUUIDText(iCurrentLine,aUUID);
+		}
+	LineFinishedL();
+	return this;
+	}
+
+MSdpElementBuilder* CBtAttributeBuilder::BuildBooleanL(TBool aBool)
+	{
+	if (iStatus == EExpectingAttrID)
+		{
+		_LIT(KErrorText,"**Error: Unexpected Boolean");
+		iCurrentLine.Append(KErrorText);
+		}
+	else
+		{
+		if (aBool)
+			{
+			_LIT(KTextTrue,"TRUE");
+			iCurrentLine.Append(KTextTrue);
+			}
+		else
+			{
+			_LIT(KTextFalse,"FALSE");
+			iCurrentLine.Append(KTextFalse);
+			}
+		}
+	LineFinishedL();
+	return this;
+	}
+
+MSdpElementBuilder* CBtAttributeBuilder::BuildStringL(const TDesC8& aString)
+	{
+	if (iStatus == EExpectingAttrID)
+		{
+		_LIT(KErrorText,"**Error: Unexpected String");
+		iCurrentLine.Append(KErrorText);
+		}
+	else
+		{
+		TBuf16<KMaxDescriptorLength> unicode;
+		CnvUtfConverter::ConvertToUnicodeFromUtf8(unicode,aString);
+		if (unicode[unicode.Length() - 1] == NULL)
+			{
+			unicode.Delete(unicode.Length() - 1, 1); // remove NULL-termination
+			}
+		iCurrentLine.Append(unicode);
+		}
+	LineFinishedL();
+	return this;
+	}
+
+MSdpElementBuilder* CBtAttributeBuilder::BuildDESL()
+	{
+	if (iIndentation == 1)
+		{
+		iEngine.AddAttributeLineL(iCurrentLine);
+		}
+	return this;
+	}
+
+MSdpElementBuilder* CBtAttributeBuilder::BuildDEAL()
+	{
+	_LIT(KTextDEAL,"Data Element Alternate List");
+	iCurrentLine.Append(KTextDEAL);
+	iEngine.AddAttributeLineL(iCurrentLine);
+	return this;
+	}
+
+MSdpElementBuilder* CBtAttributeBuilder::StartListL()
+	{
+	iIndentation++;
+	iCurrentLine.Zero();
+	__ASSERT_ALWAYS(iIndentation > 0, CBtServicesEng::Panic(EBtIndentationZeroInStartList));
+	iCurrentLine.AppendFill(' ', (iIndentation - 1) * KIndentationFactor);
+	return this;
+	}
+
+MSdpElementBuilder* CBtAttributeBuilder::EndListL()
+	{
+	_LIT(KTextEndListDashes,"--------");
+	iCurrentLine.Append(KTextEndListDashes);
+	iEngine.AddAttributeLineL(iCurrentLine);
+	iIndentation--;
+	iCurrentLine.Zero();
+	if (iIndentation)
+		{
+		iCurrentLine.AppendFill(' ', (iIndentation - 1) * KIndentationFactor); // in this case indentation has already been checked to be greater than zero
+		}
+	if (iIndentation == 1)
+		{
+		iStatus = EExpectingAttrID;
+		}
+	return this;
+	}
+
+MSdpElementBuilder* CBtAttributeBuilder::BuildURLL(const TDesC8& aURL)
+	{
+	TBuf16<KMaxDescriptorLength> unicode;
+	CnvUtfConverter::ConvertToUnicodeFromUtf8(unicode,aURL);
+	iCurrentLine.Append(unicode);
+	LineFinishedL();
+	return this;
+	}
+
+void CBtAttributeBuilder::AppendAttrMnemonic(TDes& aBuf, TUint aAttrID) const
+	{
+	for (TInt i = 0; i < iAttrIDs.Count(); i++)
+		{
+		if (iAttrIDs[i] == aAttrID)
+			{
+			aBuf.Append(*(iAttrMnemonics[i]));
+			return;
+			}
+		}
+	if (aAttrID == 0x0200 && iHandle == 0)
+		{
+		_LIT(KVersionNoPrefix,"VersionNo.: ");
+		aBuf.Append(KVersionNoPrefix);
+		}
+	else if (aAttrID == 0x0201 && iHandle == 0)
+		{
+		_LIT(KDatabaseStatePrefix,"DatabaseState: ");
+		aBuf.Append(KDatabaseStatePrefix);
+		}
+	else
+		{
+		_LIT(KTextAttrIDHex,"AttrID 0x");
+		aBuf.Append(KTextAttrIDHex);
+		aBuf.AppendNum(aAttrID,EHex);
+		_LIT(KTextColonSpace,": ");
+		aBuf.Append(KTextColonSpace);
+		}
+	}
+
+void CBtAttributeBuilder::LineFinishedL()
+	{
+	iEngine.AddAttributeLineL(iCurrentLine);
+	iCurrentLine.Zero();
+	__ASSERT_ALWAYS(iIndentation > 0, CBtServicesEng::Panic(EBtIndentationZeroInLineFinished));
+	iCurrentLine.AppendFill(' ', (iIndentation - 1) * KIndentationFactor);
+	if (iIndentation == 1)
+		{
+		iStatus = EExpectingAttrID;
+		}
+	}