bluetoothmgmt/bluetoothclientlib/avctpservices/avctpremotedevices.cpp
author Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
Tue, 27 Apr 2010 17:48:21 +0300
branchRCL_3
changeset 14 f8503e232b0c
parent 0 29b1cd4cb562
permissions -rw-r--r--
Revision: 201011 Kit: 201017

// Copyright (c) 2005-2009 Nokia Corporation and/or its subsidiary(-ies).
// All rights reserved.
// This component and the accompanying materials are made available
// under the terms of "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:
// Nokia Corporation - initial contribution.
//
// Contributors:
//
// Description:
//

/**
 @file
 @internalTechnology
*/

#include <bluetooth/logger.h>
#include <bt_sock.h>

#include "avctpremotedevices.h"
#include "avctpbody.h"
#include "avctpserviceutils.h"
#include "avctpcommon.h"
#include "avctpPriorities.h"
#include "avctpserviceutils.h"
#include "channelcontrollers.h"

using namespace SymbianAvctp;

#ifdef __FLOG_ACTIVE
_LIT8(KLogComponent, LOG_COMPONENT_AVCTP_SERVICES);
#endif

RNestableLock::RNestableLock()
	: iRefCount(0)
	, iThreadId(KInvalidThreadId)	// set to an invalid id
	{
	}

TInt RNestableLock::CreateLocal()
	{
	TInt err = iLock.CreateLocal();
	if(err == KErrNone)
		{
		err = iMetaLock.CreateLocal();
		}
	if(err != KErrNone)
		{
		Close();
		}
	return err;
	}

void RNestableLock::Close()
	{
	iLock.Close();
	iMetaLock.Close();
	iRefCount = 0;
	}

void RNestableLock::Wait()
	{
	iMetaLock.Wait();
	TThreadId currentThreadId = RThread().Id();
	if(iThreadId == TThreadId(KInvalidThreadId) || currentThreadId != iThreadId)
		{
		iMetaLock.Signal();
		iLock.Wait();
		iMetaLock.Wait();
		iThreadId = currentThreadId;
		}
	++iRefCount;
	iMetaLock.Signal();
	}

void RNestableLock::Signal()
	{
	iMetaLock.Wait();
	// Assert if current thread is stored current thread?
	if(--iRefCount == 0)
		{
		iLock.Signal();
		iThreadId = TThreadId(KInvalidThreadId);
		}
	iMetaLock.Signal();
	}

TAvctpRemoteDeviceInfo::TAvctpRemoteDeviceInfo(const TBTDevAddr& aAddr) :
	iAddr(aAddr),
	iHasSecondChannel(EFalse)
	{
	LOG_FUNC
	}

const TBTDevAddr& TAvctpRemoteDeviceInfo::RemoteAddress() const
	{
	LOG_FUNC
	return iAddr;
	}

TBool TAvctpRemoteDeviceInfo::HasSecondChannel() const
	{
	LOG_FUNC
	return iHasSecondChannel;
	}

void TAvctpRemoteDeviceInfo::SetHasSecondChannel(TBool aHasSecondChannel)
	{
	LOG_FUNC
	iHasSecondChannel = aHasSecondChannel;
	}

/**
Two phase constructor of CAvctpRemoteDevices which deals with connection and disconnection 
of a remote device. 
@param aNotify provides access to the MAvctpEventNotify
@param aSocketServ a connected comms session
@internalComponent
*/
CAvctpRemoteDevices* CAvctpRemoteDevices::NewL(MAvctpEventNotify& aNotify, RSocketServ& aSocketServ, SymbianAvctp::TPid aPid)
	{
	LOG_STATIC_FUNC

	CAvctpRemoteDevices* self = CAvctpRemoteDevices::NewLC(aNotify, aSocketServ, aPid);
	CleanupStack::Pop(self);
	return self;
	}
	
/**
Two phase constructor of CAvctpRemoteDevices which deals with connection and disconnection 
of a remote device. 
This leaves newly created object on cleanupstack
@param aAvctpBody provides access to the MAvctpEventNotify
@param aSocketServ a connected comms session
@internalComponent
*/
CAvctpRemoteDevices* CAvctpRemoteDevices::NewLC(MAvctpEventNotify& aNotify, RSocketServ& aSocketServ, SymbianAvctp::TPid aPid)
	{
	LOG_STATIC_FUNC

	CAvctpRemoteDevices* self = new(ELeave) CAvctpRemoteDevices(aNotify, aSocketServ);
	CleanupStack::PushL(self);
	self->ConstructL(aPid);
	return self;	
	}

/**
c'tor
@internalComponent
*/
CAvctpRemoteDevices::CAvctpRemoteDevices(MAvctpEventNotify& aNotify, RSocketServ& aSocketServ):
 	iNotify(aNotify),
	iSocketServ(aSocketServ)
	{
	LOG_FUNC
	}
	

/** 
@internalComponent
*/
void CAvctpRemoteDevices::ConstructL(SymbianAvctp::TPid aPid)
	{
	LOG_FUNC

	User::LeaveIfError(iLock.CreateLocal()); 
	
	iPid = aPid;
	iState = EListening;
	
	iPrimaryChannelController = CPrimaryChannelController::NewL(*this, iSocketServ, aPid);	
	iPrimaryChannelController->Listen();
	}

	
/**
d'tor
@internalComponent
*/	
CAvctpRemoteDevices::~CAvctpRemoteDevices()
	{
	LOG_FUNC
	iLock.Wait();
	iLock.Close();

	iRemoteAddrs.Close();
	delete iSecondaryChannelController;
	delete iPrimaryChannelController;
	}

/**
Starts a connection to a remote device
@param aBTDevice the remote device to connect to
@internalComponent
*/
TInt CAvctpRemoteDevices::PrimaryChannelAttachRequest(const TBTDevAddr& aBTDevice)
	{
	LOG_FUNC
	
	iLock.Wait();

	TInt err = KErrRepeatConnectionAttempt;

	// Start the connection attempt
	
	if (iState == EListening)
		{
		if (!IsAttached(aBTDevice))
			{
			iState = ECreatingControlLink;
			iPrimaryChannelController->AttachRequest(aBTDevice);
			err = KErrNone;
			}
		}
	
	if (err != KErrNone)
		{
		iState = EListening;
		}
	
	iLock.Signal();
	return err;
	}

/**
Called after a Attach Indicate. If the connection hasn't been accepted it send back to the server
a refuse attach ioctl, and the client won't be notified through the controlling channel
@param aBTDevice the remote device to add
@param aClientsAccepts ETrue if the client has accepted the Attach Indication or
					   EFalse if the client has denied the Attach Indication
@param aChannel the controlling channel through the indication has been received
@internalComponent
*/
TInt CAvctpRemoteDevices::PropagateAttachRsp(const TBTDevAddr& aBTDevice, 
							  			      TBool aClientsAccepts,
							  			      TInt aChannel)
	{
	LOG_FUNC

	TInt err = KErrNone;

	if (aClientsAccepts) 
		{
		if (err == KErrNone && !IsAttached(aBTDevice) && aChannel == KAvctpPrimaryChannel)
			{
			//only add device on primary control channel connection
			TAvctpRemoteDeviceInfo info(aBTDevice);
			err = iRemoteAddrs.Append(info);
			}
		}
	else
		{
		__ASSERT_DEBUG(aChannel == KAvctpPrimaryChannel || aChannel == KAvctpSecondaryChannel, Panic(EAvctpInvalidChannel));
		
		CBaseChController* pointer = (aChannel == KAvctpPrimaryChannel) ? ((CBaseChController*)iPrimaryChannelController) : ((CBaseChController*)iSecondaryChannelController);
		
		__ASSERT_DEBUG(pointer, Panic(EAvctpNullControllerChannel));
		pointer->RefuseAttach(aBTDevice);	// won't receive a confirmation
		}	
	
	return err;
	}
	
/**
Explicitly disconnect from the give remote device.
@internalComponent
*/
TInt CAvctpRemoteDevices::PrimaryChannelDetachRequest(const TBTDevAddr& aBTDevice)
	{
	LOG_FUNC
	
	iLock.Wait();
	
	TInt err = KErrFirstChannelNotAttached;
	const TAvctpRemoteDeviceInfo* info = RemoteDeviceInfo(aBTDevice);
	if (info)
		{
		iPrimaryChannelController->DetachRequest(aBTDevice);
		err = KErrNone;
		}
	
	iLock.Signal();
	
	// else covered by ret
	return err;
	}

void CAvctpRemoteDevices::PrimaryChannelCancelAttach(const TBTDevAddr& aBTDevice)
	{
	LOG_FUNC
	
	iLock.Wait();
	
	if (!IsAttached(aBTDevice) && iState == ECreatingControlLink)
		{
		// the notifications are only propagated if the state is indicating an
		// outstanding request. So, simply changing the state to listening will avoid
		// the notification to be propagated. 
		// We also need to notify the stack that we are not interested so to have a consistent state
		// It is safe to call RefuseAttach that deal with this scenario server side.
		iPrimaryChannelController->RefuseAttach(aBTDevice);
		iState = EListening;	
		}
	
	iLock.Signal();
	}

TInt CAvctpRemoteDevices::SecondaryChannelAttachRequest(const TBTDevAddr& aBTDevice)
	{
	LOG_FUNC
	
	iLock.Wait();
	TInt err = KErrFirstChannelNotAttached;
	const TAvctpRemoteDeviceInfo* info = RemoteDeviceInfo(aBTDevice);
	if (info)
		{
		err = KErrRepeatConnectionAttempt;
		if (!info->HasSecondChannel())
			{
			__ASSERT_DEBUG(iSecondaryChannelNotify, Panic(EAvctpSecondChannelNotPresent));
			if (iState == EListening)
				{
				iState = ECreatingSecondLink;
				iSecondaryChannelController->AttachRequest(aBTDevice);
				err = KErrNone;
				}
			}
		}
	iLock.Signal();
	return err;
	}

TInt CAvctpRemoteDevices::SecondaryChannelDetachRequest(const TBTDevAddr& aBTDevice)
	{
	LOG_FUNC
	
	iLock.Wait();
	TInt err = KErrSecondChannelNotAttached;
	const TAvctpRemoteDeviceInfo* info = RemoteDeviceInfo(aBTDevice);
	if (info)
		{
		if (info->HasSecondChannel())
			{
			__ASSERT_DEBUG(iSecondaryChannelNotify, Panic(EAvctpSecondChannelNotPresent));
			iSecondaryChannelController->DetachRequest(aBTDevice);
			err = KErrNone;
			}
		}
	iLock.Signal();
	return err;
	}

void CAvctpRemoteDevices::SecondaryChannelCancelAttach(const TBTDevAddr& aBTDevice)
	{
	LOG_FUNC
	
	iLock.Wait();
	
	if (IsAttached(aBTDevice) && iState == ECreatingSecondLink)
		{
		// the notifications are only propagated if the state is indicating an
		// outstanding request. So, simply changing the state to listening will avoid
		// the notification to be propagated.
		iState = EListening;	
		}
	
	iLock.Signal();
	}

void CAvctpRemoteDevices::RemoveRemoteDevice(const TBTDevAddr& aBTDevice)
	{
	LOG_FUNC
	
	__DEBUG_ONLY
		(
		TBuf<KBTAddressLength> address;
		aBTDevice.GetReadable(address);
		)
	
	LOG1(_L("BT Device 0x%S"), &address);

	TInt index;	  
	for(index= 0; index < iRemoteAddrs.Count(); index++)
		{
		if (iRemoteAddrs[index].RemoteAddress() == aBTDevice)
			{
			iRemoteAddrs.Remove(index);
			break; // since there should only be one remote device per TBTDevAddr
			}
		}
	}

const TAvctpRemoteDeviceInfo* CAvctpRemoteDevices::RemoteDeviceInfo(const TBTDevAddr& aBTDevice) const
	{
	LOG_FUNC

	const TAvctpRemoteDeviceInfo* deviceInfo = NULL;
	
	TInt index;
	for (index = 0; index < iRemoteAddrs.Count(); index++)
		{
		if (iRemoteAddrs[index].RemoteAddress() == aBTDevice)
			{
			deviceInfo = &iRemoteAddrs[index];
			break; // there should only be one remote device on each TBTDevAddr
			}
		}
	
	return deviceInfo;
	}

TBool CAvctpRemoteDevices::IsAttached(const TBTDevAddr& aBTDevice) const
	{
	LOG_FUNC
	
	iLock.Wait();
	
	TBool ans = RemoteDeviceInfo(aBTDevice) ? ETrue : EFalse;
	
#ifdef _DEBUG
//	// Check there is only one remote device on this TBTDevAddr in the array
	TInt numDevices = 0;
	TInt index;
	for (index = 0; index < iRemoteAddrs.Count(); index++)
		{
		if (iRemoteAddrs[index].RemoteAddress() == aBTDevice)
			{
			numDevices++;
			}
		}
	__ASSERT_DEBUG(((ans && numDevices == 1) || (!ans && numDevices == 0)), Panic(EAvctpRemoteAddressOccursMultipleTimes));
#endif // _DEBUG
	
	LOG1(_L("IsAttached() result is: %d"), ans)
	
	iLock.Signal();
	
	return ans;
	}

/**
All Errors to do with a remote device will result in a disconnect Ind happening
because all device errors are currently fatal. NB since the actual error doesn't
matter to the client they don't get informed of it.
*/
void CAvctpRemoteDevices::NotifyError(const TBTDevAddr& aBTDevice, TInt aError, TInt aChannel)
	{
	LOG_FUNC
	
	MAvctpEventNotify* notify = aChannel == KAvctpPrimaryChannel ? &iNotify : iSecondaryChannelNotify; 
	notify->MaenErrorNotify(aBTDevice, aError);	
	// don't do anything here as the client may have closed the RAvctp object after the NotifyError
	}

void CAvctpRemoteDevices::SetSecondaryChannelNotifyL(MAvctpEventNotify* aSecondChannelNotify)
	{
	LOG_FUNC
	
	iLock.Wait();
	
	__ASSERT_DEBUG(!aSecondChannelNotify || !iSecondaryChannelNotify, Panic(EAvctpSecondaryChannelNotifyAlreadyAssigned));

	if(aSecondChannelNotify)
		{
		iSecondaryChannelNotify = aSecondChannelNotify;
		
		iSecondaryChannelController = CSecondaryChannelController::NewL(*this, iSocketServ, iPid);
		iSecondaryChannelController->Listen();
		}
	else
		{
		delete iSecondaryChannelController;
		iSecondaryChannelController = NULL;
		}
	
	iLock.Signal();
	}

void CAvctpRemoteDevices::PrimaryChannelAttachConfirm(const TBTDevAddr& aAddr, TInt aMtu, TInt aError)
	{
	LOG_FUNC
	
	iLock.Wait();
	
	// state must be:
	// ECreatingControlLink because we have requested to be attached.
	// EListening because we had asked to be attached but then we canceled the request.
	__ASSERT_DEBUG(iState == ECreatingControlLink || iState == EListening, Panic(EAvctpInvalidChannelState));
	
	if (iState == ECreatingControlLink)
		{
		iNotify.MaenAttachConfirm(aAddr, aMtu, aError);
		if (aError == KErrNone && !IsAttached(aAddr))
			{
			TAvctpRemoteDeviceInfo info(aAddr);
			aError = iRemoteAddrs.Append(info);
			}
		}
	
	iState = EListening;
	iPrimaryChannelController->Listen();
	
	iLock.Signal();
	}

void CAvctpRemoteDevices::PrimaryChannelAttachIndicate(const TBTDevAddr& aAddr, TInt aMtu)
	{
	LOG_FUNC
	
	iLock.Wait();
	
	TBool clientAccepts = EFalse;
	iNotify.MaenAttachIndicate(aAddr, aMtu, clientAccepts);
	TInt err = PropagateAttachRsp(aAddr, clientAccepts, KAvctpPrimaryChannel);
	// we can have an error if the array append fails 
	if (err == KErrNone)
		{
		if (clientAccepts)
			{
			iPrimaryChannelController->AgreeAttachment(aAddr);
			// iSecondaryChannelController->AttachPassively(aAddr);
			}
		}
	else 
		{
		NotifyError(aAddr, err, KAvctpPrimaryChannel);
		}

	// whatever happen (even an error) we need to listen anyway.
	iState = EListening;
	iPrimaryChannelController->Listen();
	
	iLock.Signal();
	}

void CAvctpRemoteDevices::PrimaryChannelDetachConfirm(const TBTDevAddr& aAddr, TInt aError)
	{
	LOG_FUNC
	
	iLock.Wait();
	
	if (aError == KErrNone)
		{
		const TAvctpRemoteDeviceInfo* info = RemoteDeviceInfo(aAddr);
		__ASSERT_DEBUG(info, Panic(EAvctpRemoteDeviceNotConnected));
		
		RemoveRemoteDevice(aAddr);
		iNotify.MaenDetachConfirm(aAddr, KErrNone);
		}
	else
		{
		NotifyError(aAddr, aError, KAvctpPrimaryChannel);
		}
	
	// whatever happens we need to listen
	iState = EListening;
	iPrimaryChannelController->Listen();
	
	iLock.Signal();
	}

void CAvctpRemoteDevices::PrimaryChannelDetachIndicate(const TBTDevAddr& aAddr)
	{
	LOG_FUNC
	
	iLock.Wait();
	
	// it can happen that we are in ECreatingSecondLink and the remote disconnects
	// the first channel.
	// server side we first notify the secondary channel, then the first one. 
	// But we are not guarantee that the secondary channel will be served first
	// so we need to save the oldstate to update after this process.
	// in this case, when we'll receive the secondary channel event we are in the 
	// correct state.
	// even if the state at the end won't be listening, we have to listen on the 
	// primary channel anyway.
	
	TState oldState = iState;
	
    const TAvctpRemoteDeviceInfo* info = RemoteDeviceInfo(aAddr);
    __ASSERT_DEBUG(info, Panic(EAvctpRemoteDeviceNotConnected));

    RemoveRemoteDevice(aAddr);
    iNotify.MaenDetachIndicate(aAddr);
	
	// we go to the previous state (before detaching the primary channel). see the comment above
	// for more info
	iState = oldState;
	// but we need to listen on the primary channel anyway.
	iPrimaryChannelController->Listen();
	
	iLock.Signal();
	}

void CAvctpRemoteDevices::PrimaryChannelIoctlError(const TBTDevAddr& aAddr, TInt aError)
	{
	LOG_FUNC
	
	iLock.Wait();
	
	iNotify.MaenErrorNotify(aAddr, aError);
	
	// after having notified the error we must back to listen, despite the state we were before.
	iState = EListening;
	iPrimaryChannelController->Listen();
	
	iLock.Signal();
	}

void CAvctpRemoteDevices::PrimaryChannelAgreementError(const TBTDevAddr& aAddr, TInt __DEBUG_ONLY(aError))
	{
	LOG_FUNC
	
	iLock.Wait();
	
	const TAvctpRemoteDeviceInfo* info = RemoteDeviceInfo(aAddr);
	__ASSERT_DEBUG(info, Panic(EAvctpRemoteDeviceNotConnected));
	__ASSERT_DEBUG(!info->HasSecondChannel(), Panic(EAvctpSecondaryChannelUnexpected));
	__ASSERT_DEBUG(aError == KErrMuxerNotFound || aError == KErrNoMemory, Panic(EAvctpUnexpectedErrorCode));
	
	RemoveRemoteDevice(aAddr);
	iNotify.MaenDetachIndicate(aAddr);
	iState = EListening;
	iPrimaryChannelController->Listen();
	
	iLock.Signal();
	}

void CAvctpRemoteDevices::SecondaryChannelAttachConfirm(const TBTDevAddr& aAddr, TInt aMtu, TInt aError)
	{
	LOG_FUNC
	
	iLock.Wait();
	
	// state must be:
	// ECreatingControlLink because we have requested to be attached.
	// EListening because we had asked to be attached but then we canceled the request.
	__ASSERT_DEBUG(iState == ECreatingSecondLink || iState == EListening, Panic(EAvctpInvalidChannelState));
		
	if (iState == ECreatingSecondLink)
		{
		iSecondaryChannelNotify->MaenAttachConfirm(aAddr, aMtu, aError);
		if (aError == KErrNone)
			{
			TAvctpRemoteDeviceInfo* info = const_cast<TAvctpRemoteDeviceInfo*>(RemoteDeviceInfo(aAddr));
			__ASSERT_DEBUG(info, Panic(EAvctpRemoteDeviceNotConnected));
			info->SetHasSecondChannel(ETrue);
			}
		else
			{
			if (aError == KErrMuxerShutDown)
				{
				RemoveRemoteDevice(aAddr);
				}
			}
		}
	iState = EListening;
	iSecondaryChannelController->Listen();
	
	iLock.Signal();
	}

void CAvctpRemoteDevices::SecondaryChannelAttachIndicate(const TBTDevAddr& aAddr, TInt aMtu)
	{
	LOG_FUNC
	
	iLock.Wait();
	
	TAvctpRemoteDeviceInfo* info = const_cast<TAvctpRemoteDeviceInfo*>(RemoteDeviceInfo(aAddr));

	// if info is NULL it probably means that the first channel attach indication was refused or
	// the attach request was cancelled. However, in that case, we do nothing.
	if (info)
		{
		TBool clientAccepts = EFalse;
		if(iSecondaryChannelNotify)
			{
			iSecondaryChannelNotify->MaenAttachIndicate(aAddr, aMtu, clientAccepts);
	#ifdef _DEBUG		
			TInt err = 
	#endif
			PropagateAttachRsp(aAddr, clientAccepts, KAvctpSecondaryChannel);	
			
			// err must always be KErrNone when PropagateConnectRsp is called on the secondary channel
			__ASSERT_DEBUG(err == KErrNone, Panic(EAvctpUnexpectedErrorCode));
			
			if (clientAccepts)
				{
				iSecondaryChannelController->AgreeAttachment(aAddr);
				info->SetHasSecondChannel(ETrue);
				}
			}
		else // No-one's interested
			{
			iSecondaryChannelController->RefuseAttach(aAddr);
			}
		}
	iState = EListening;
	iSecondaryChannelController->Listen();
	
	iLock.Signal();
	}

void CAvctpRemoteDevices::SecondaryChannelDetachConfirm(const TBTDevAddr& aAddr, TInt aError)
	{
	LOG_FUNC
	
	iLock.Wait();
	
	if (aError == KErrNone)
		{
		__ASSERT_DEBUG(iSecondaryChannelNotify, Panic(EAvctpSecondChannelNotPresent));
		
		TAvctpRemoteDeviceInfo* info = const_cast<TAvctpRemoteDeviceInfo*>(RemoteDeviceInfo(aAddr));
		__ASSERT_DEBUG(info, Panic(EAvctpRemoteDeviceNotConnected));
		__ASSERT_DEBUG(info->HasSecondChannel(), Panic(EAvctpSecondChannelNotPresent));
		iSecondaryChannelNotify->MaenDetachConfirm(aAddr, KErrNone);
		info->SetHasSecondChannel(EFalse);
		}
	else
		{
		NotifyError(aAddr, aError, KAvctpSecondaryChannel);
		}

	iState = EListening;
	iSecondaryChannelController->Listen();
	
	iLock.Signal();
	}

void CAvctpRemoteDevices::SecondaryChannelDetachIndicate(const TBTDevAddr& aAddr)
	{
	LOG_FUNC
	
	iLock.Wait();
	
	// In the case where we haven't got a secondary channel notify we may still get
	// a detach indicate if the refusal for the attach indicate has not been 
	// processed before the remote disconnects.  
	if(iSecondaryChannelNotify)
		{
		iSecondaryChannelNotify->MaenDetachIndicate(aAddr);
		TAvctpRemoteDeviceInfo* info = const_cast<TAvctpRemoteDeviceInfo*>(RemoteDeviceInfo(aAddr));
        if (info && info->HasSecondChannel())
            {
            info->SetHasSecondChannel(EFalse);
            }
		}
#ifdef _DEBUG
	else
		{
		// in this case we should not have any remote device info - check
		TAvctpRemoteDeviceInfo* info = const_cast<TAvctpRemoteDeviceInfo*>(RemoteDeviceInfo(aAddr));
		__ASSERT_ALWAYS(!info || !info->HasSecondChannel(), Panic(EDetachIndicateForSecondChannelWithNoConsumer));
		}
#endif
	
	iState = EListening;
	iSecondaryChannelController->Listen();
	
	iLock.Signal();
	}

void CAvctpRemoteDevices::SecondaryChannelIoctlError(const TBTDevAddr& aAddr, TInt aError)
	{
	LOG_FUNC
	
	iLock.Wait();
	
	__ASSERT_DEBUG(iSecondaryChannelNotify, Panic(EAvctpSecondChannelNotPresent));
	iSecondaryChannelNotify->MaenErrorNotify(aAddr, aError);
	// after having notified the error we must back to listen, despite the state we were before.
	iState = EListening;
	iSecondaryChannelController->Listen();
	
	iLock.Signal();
	}

void CAvctpRemoteDevices::SecondaryChannelAgreementError(const TBTDevAddr& aAddr, TInt __DEBUG_ONLY(aError))
	{
	LOG_FUNC
	
	iLock.Wait();
	
	TAvctpRemoteDeviceInfo* info = const_cast<TAvctpRemoteDeviceInfo*>(RemoteDeviceInfo(aAddr));
	__ASSERT_DEBUG(info, Panic(EAvctpRemoteDeviceNotConnected));
	__ASSERT_DEBUG(info->HasSecondChannel(), Panic(EAvctpSecondaryChannelUnexpected));
	__ASSERT_DEBUG(aError == KErrMuxerNotFound, Panic(EAvctpUnexpectedErrorCode));
	
	RemoveRemoteDevice(aAddr);
	iSecondaryChannelNotify->MaenDetachIndicate(aAddr);
	iState = EListening;
	iSecondaryChannelController->Listen();
	
	iLock.Signal();
	}