smsprotocols/smsstack/smsprot/Src/smspsend.cpp
author Pat Downey <patd@symbian.org>
Wed, 01 Sep 2010 12:40:21 +0100
branchRCL_3
changeset 20 07a122eea281
parent 19 630d2f34d719
permissions -rw-r--r--
Revert incorrect RCL_3 drop: Revision: 201035 Kit: 201035

// Copyright (c) 1997-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:
// Implementation for the CSmsuActiveBase based class CSmsMessageSend.
// 
//

/**
 @file
 @internalAll
*/

#include "smspsend.h"
#include "smspstor.h"
#include "smspmain.h"
#include "smsuset.h"
#include "smsulog.h"
#include "exterror.h"
#include "gsmubuf.h"
#include "SmsuTimer.h"
#include <logwraplimits.h>


/** 
 *  Static object constructor for CSmsMessageSend.
 *
 *  @param aSegmentationStore  Segmentation Store.
 *  @param aSmsMessaging       ETel SMS messaging subsession.
 *  @param aSmsSettings        The global SMS settings.
 *  @param aMobileSmsCaps      SMS messaging capabilities of TSY/phone.
 *  @param aPriority           Priority to use for the Active Object.
 *  @param aSmspSetBearer      Set bearer object.
 */
CSmsMessageSend* CSmsMessageSend::NewL(CSmsSegmentationStore& aSegmentationStore,
									   const TSmsSettings& aSmsSettings,
									   const RMobileSmsMessaging::TMobileSmsCapsV1& aMobileSmsCaps,
									   RMobileSmsMessaging& aSmsMessaging,
									   TInt aPriority,
									   CSmspSetBearer& aSmspSetBearer)
	{
	LOGSMSPROT1("CSmsMessageSend::NewL()");

	CSmsMessageSend*  self = new (ELeave) CSmsMessageSend(aSegmentationStore,
														  aSmsSettings,
														  aMobileSmsCaps,
														  aSmsMessaging,
														  aPriority,
														  aSmspSetBearer);
	CleanupStack::PushL(self);
	self->ConstructL();
	CleanupStack::Pop(self);

	return self;
	} // CSmsMessageSend::NewL


/**
 *  2nd Phase of construction.
 *  
 *  Creates CSmsTimeout object and the SMS event logger.
 */
void CSmsMessageSend::ConstructL()
	{
	LOGSMSPROT1("CSmsMessageSend::ConstructL()");

	iSmsEventLogger = CSmsEventLogger::NewL(iSegmentationStore.FileSession(),
											Priority());

	ConstructTimeoutL();
	} // CSmsMessageSend::ConstructL


/** 
 *  Default constructor.
 *
 *  @param aSegmentationStore  Segmentation Store.
 *  @param aSmsSettings        The global SMS settings.
 *  @param aMobileSmsCaps      SMS messaging capabilities of TSY/phone.
 *  @param aSmsMessaging       ETel SMS messaging subsession.
 *  @param aPriority           Priority to use for the Active Object.
 *  @param aSmspSetBearer      Set bearer object.
 */
CSmsMessageSend::CSmsMessageSend(CSmsSegmentationStore& aSegmentationStore,
								 const TSmsSettings& aSmsSettings,
								 const RMobileSmsMessaging::TMobileSmsCapsV1& aMobileSmsCaps,
								 RMobileSmsMessaging& aSmsMessaging,
								 TInt aPriority,
								 CSmspSetBearer& aSmspSetBearer)
  : CSmsuActiveBase(aPriority),
	iState(ESmsMessageSendIdle),
	iSegmentationStore(aSegmentationStore),
	iSmspSetBearer(aSmspSetBearer),
	iSmsSettings(aSmsSettings),
	iMobileSmsCaps(aMobileSmsCaps),
	iSmsMessaging(aSmsMessaging),
	iMobileSmsSendAttributesV1Pckg(iMobileSmsSendAttributesV1),
	iSmsArray(8),
	iSegmentSequenceNumber(-1)
	{
	// NOP
	} // CSmsMessageSend::CSmsMessageSend


/** 
 *  Standard destructor.
 */
CSmsMessageSend::~CSmsMessageSend()
	{
	Cancel();
	delete iSmsEventLogger;
	} // CSmsMessageSend::~CSmsMessageSend


void CSmsMessageSend::Start(CSmsMessage& aSmsMessage, TInt aOptions,
							const TSmsAddr& aSmsAddr, TRequestStatus& aStatus)
	{
	LOGSMSPROT1("CSmsMessageSend::Start()");

	__ASSERT_DEBUG(iState == ESmsMessageSendIdle,SmspPanic(KSmspPanicUnexpectedState));

	//
	// Store the request status that we will complete when finished...
	//
	Queue(aStatus);

	//
	// Initialise member data...
	//
	iSmsMessage=&aSmsMessage;
	iSmsMessage->ParsedToFromAddress(iToFromTelNumber);
	iOptions = aOptions;
	iSmsAddr = aSmsAddr;

	iSmsPDUData.iType      = aSmsMessage.Type();
	iSmsPDUData.iTotal     = 0;
	iSmsPDUData.iSent      = 0;
	iSmsPDUData.iDelivered = 0;

	iSendError = KErrNone;
	iSmsEventLogger->Event().SetId(KLogNullId);

	//
	// Begin by setting the bearer...
	//
	iState = ESmsMessageSendSetBearer;
	iSmspSetBearer.NotifyBearerSet(iStatus);
	SetActive();
	} // CSmsMessageSend::Start


void CSmsMessageSend::DoRunL()
	{
	LOGSMSPROT3("CSmsMessageSend::RunL(): iStatus=%d, iState=%d", iStatus.Int(), iState);

	switch (iState)
		{
		case ESmsMessageSendSetBearer:
			{
			//
			// If setting of bearer was successful then send the message.
			//
			// The TSY will complete with KErrNotSupported if it doesn't
			// support setting the send bearer.
			//
			if (iStatus.Int() == KErrNone  ||
				iStatus.Int() == KErrNotSupported)
				{
				if (iSmsEventLogger->ClientAvailable())
					{
					CreateLogServerEvent();
					}
				else
					{
					SegmentMessage();
					}
				}
			}
			break;

		case ESmsMessageSendCreatingLogServerEvent:
			{
			//
			// If a log event was created ok, then store the id...
			//
			if (iStatus.Int() == KErrNone)
				{
				iSmsMessage->SetLogServerId(iSmsEventLogger->Event().Id());
				}
			else
				{
				iSmsEventLogger->Event().SetId(KLogNullId);
				}

			//
			// Segment the message. This will send the first PDU as well...
			//
			SegmentMessage();
			}
			break;

		case ESmsMessageSendSegmentingMessage:
			{
			//
			// If the previous PDU was sent ok, then send the next one...
			//
			if (iStatus.Int() == KErrNone)
				{
				SendNextPDU();
				}
			else
				{
				//
				// An error occured. Save the error for later and either
				// update the log event, or drop to the end of RunL() so
				// that Complete() is called...
				//
				iSendError = iStatus.Int();

				if (iSmsEventLogger->Event().Id() != KLogNullId)
					{
					UpdateLogServerEvent();
					}
				}
			}
			break;

		case ESmsMessageSendPDU:
			{
			//
			// If the PDU is sent with an acknowledgement, then decode the
			// submit report...
			//
			if (iMobileSmsCaps.iSmsControl & RMobileSmsMessaging::KCapsSendWithAck  &&
				iMobileSmsSendAttributesV1.iFlags & RMobileSmsMessaging::KGsmSubmitReport)
				{
				DecodeSubmitReportL();
				}

			if (iStatus.Int() == KErrNone  &&  !IsRPError())
				{
				LOGSMSIF2("CSmsMessageSend::RunL(): Submit ACK'd MsgRef=%d",
				          iMobileSmsSendAttributesV1.iMsgRef);
				}
			else if (IsRPError())
				{
				LOGSMSIF2("CSmsMessageSend::RunL(): Submit N'ACK'D MsgRef=%d",
				          iMobileSmsSendAttributesV1.iMsgRef);
				}
			else
				{
				LOGSMSIF2("CSmsMessageSend::RunL(): MsgRef=%d",
				          iMobileSmsSendAttributesV1.iMsgRef);
				}

			//
			// If the PDU was sent okay, then update the segmentation store...
			//
			if (iStatus.Int() ==KErrNone)
				{
				iSegmentSequenceNumber++; 
				UpdateSegmentationStore();
				}
			else
				{
				//
				// An error occured. Save the error for later and either
				// update the log event, or drop to the end of RunL() so
				// that Complete() is called...
				//
				iSendError = iStatus.Int();

				if (iSmsEventLogger->Event().Id() != KLogNullId)
				 	{
				 	UpdateLogServerEvent();
				 	}
				}
			}
			break;

		case ESmsMessageSendUpdatingSegmentationStore:
			{
			if (iStatus.Int() == KErrNone)
				{
				//
				// The update has been successful, so update the log event or
				// send the next PDU. If the log event is updated, then it
				// will then send the next PDU...
				// 
				if (iSmsEventLogger->Event().Id() != KLogNullId)
					{
					UpdateLogServerEvent();
					}
				else
					{
					SendNextPDU();
					}
				}
			else
				{
				//
				// An error occured. Save the error for later and either
				// update the log event, or drop to the end of RunL() so
				// that Complete() is called...
				//
				iSendError = iStatus.Int();

				if (iSmsEventLogger->Event().Id() != KLogNullId)
					{
					UpdateLogServerEvent();
					}
				}
			}
			break;

		case ESmsMessageSendUpdatingLogEvent:
			{
			//
			// Ignore log server error and continue with the next PDU if
			// the last send was okay, otherwise drop to the bottom of the
			// RunL() and Complete().
			//
			if (iSendError == KErrNone)
				{
				SendNextPDU();
				}
			}
			break;

		default:
			{
			SmspPanic(KSmspPanicUnexpectedState);
			}
			break;
		};

	//
	// DoRunL() will now return to CSmsuActiveBase which if the object
	// is not active, will call Complete().
	//
	} // CSmsMessageSend::DoRunL


void CSmsMessageSend::DoCancel()
	{
	LOGSMSPROT2("CSmsMessageSend::DoCancel(): iState=%d", iState);

	TimedSetActiveCancel();

	switch (iState)
		{
		case ESmsMessageSendSetBearer:
			{
			iSmspSetBearer.Cancel();
			}
			break;

		case ESmsMessageSendSegmentingMessage:
			{
			// NOP
			}
			break;

		case ESmsMessageSendCreatingLogServerEvent:
		case ESmsMessageSendUpdatingLogEvent:
			{
			iSmsEventLogger->Cancel();
			}
			break;

		case ESmsMessageSendPDU:
			{
			iSmsMessaging.CancelAsyncRequest(EMobileSmsMessagingSendMessage);
			}
			break;

		case ESmsMessageSendUpdatingSegmentationStore:
			{
			// NOP
			}
			break;

		default:
			{
			SmspPanic(KSmspPanicUnexpectedState);
			}
			break;
		};

	iSendError = KErrCancel;

	//
	// Handle completion of this Active Object. Note that the object
	// may well still be active at this point...
	//
	if (TimedOut())
		{
		Complete(KErrTimedOut);
		}
	else
		{
		Complete(KErrCancel);
		}
	} // CSmsMessageSend::DoCancel


/**
 *  Decodes SMS-SUBMIT-REPORT PDU and checks if it is SMS-SUBMIT-REPORT for
 *  RP-ERROR.
 *  
 *  @leave Leaves if CSmsBuffer::NewL or CSmsMessage::NewL leaves.
 *  
 */
void CSmsMessageSend::DecodeSubmitReportL()
	{
	LOGSMSPROT1("CSmsMessageSend::DecodeSubmitReportL()");

	//
	// Only try to decode the submit report if error is a RP-error, in which case there should be a valid PDU
	//
	if (IsRPError())
		{
		TGsmSms sms;
		sms.SetPdu(iMobileSmsSendAttributesV1.iSubmitReport);
		CSmsBuffer* buffer=CSmsBuffer::NewL();

		// try to create a sms message from the submit report PDU (smsMessage takes ownership of the buffer)
		CSmsMessage*  smsMessage = CSmsMessage::NewL(iSegmentationStore.FileSession(),
													 sms,buffer,IsRPError(),ETrue);
		CleanupStack::PushL(smsMessage);

		if (smsMessage->SmsPDU().Type() == CSmsPDU::ESmsSubmitReport)
			{
			CSmsSubmitReport&  submitReport =(CSmsSubmitReport&) smsMessage->SmsPDU();
			TInt  failureCause = submitReport.FailureCause();

			if (failureCause != TSmsFailureCause::ESmsErrorFree)
				{
				iStatus = FailureCauseToError(failureCause);
				}
			}
		else
			{
			LOGSMSPROT3("Invalid PDU Type = %d with iStatus = %d",  smsMessage->SmsPDU().Type(),  iStatus.Int()  );
			}

		CleanupStack::PopAndDestroy(smsMessage);
		}
	} // CSmsMessageSend::DecodeSubmitReportL


/**
 *  Checks if TSY completed send message request with RP-error.
 *  
 *  @return boolean.
 */
TBool CSmsMessageSend::IsRPError() const
	{
	LOGSMSPROT1("CSmsMessageSend::IsRPError()");
	
	TBool  isRPError = EFalse;

	switch (iStatus.Int())
		{
		case KErrGsmSMSUnassignedNumber:
		case KErrGsmSMSOperatorDeterminedBarring:
		case KErrGsmSMSCallBarred:
		case KErrGsmSMSReserved:
		case KErrGsmSMSNetworkFailure:
		case KErrGsmSMSShortMessageTransferRejected:
		case KErrGsmSMSMemoryCapacityExceeded:
		case KErrGsmSMSDestinationOutOfOrder:
		case KErrGsmSMSUnidentifiedSubscriber:
		case KErrGsmSMSFacilityRejected:
		case KErrGsmSMSUnknownSubscriber:
		case KErrGsmSMSNetworkOutOfOrder:
		case KErrGsmSMSTemporaryFailure:
		case KErrGsmSMSCongestion:
		case KErrGsmSMSResourcesUnavailable:
		case KErrGsmSMSRequestedFacilityNotSubscribed:
		case KErrGsmSMSRequestedFacilityNotImplemented:
		case KErrGsmSMSInvalidShortMessageTransferReferenceValue:
		case KErrGsmSMSUnspecifiedInvalidMessage:
		case KErrGsmSMSInvalidMandatoryInformation:
		case KErrGsmSMSNonExistentMessageType:
		case KErrGsmSMSIncompatibleMessageWithSmsProtocolState:
		case KErrGsmSMSInformationElementNotImplemented:
		case KErrGsmSMSUnspecifiedProtocolError:
		case KErrGsmSMSUnspecifiedInterworkingError:
			{
			isRPError = ETrue;
			}
			break;

		default:
			{
			// NOP
			}
			break;
		}

	return isRPError;
	} // CSmsMessageSend::IsRPError


void CSmsMessageSend::SegmentMessage()
	{
	LOGSMSPROT1("CSmsMessageSend::SegmentMessage()");

	iState = ESmsMessageSendSegmentingMessage;

	TRAPD(ret,DoSegmentMessageL());
	
	CompleteMyself(ret);
	} // CSmsMessageSend::SegmentMessage


void CSmsMessageSend::DoSegmentMessageL()
	{
	LOGSMSPROT1("CSmsMessageSend::DoSegmentMessageL()");

	TInt reference=0;
	if (iSmsMessage->Type()==CSmsPDU::ESmsCommand)
		{
		CSmsCommand& command=(CSmsCommand&) iSmsMessage->SmsPDU();

		CSmsSegmentationStore::RSmsSegmentationStoreRefStatusArray referencearray;
		CleanupClosePushL(referencearray);

		if (!iSegmentationStore.AddCommandL(iSmsAddr,*iSmsMessage, referencearray))
			{
			User::Leave(KErrNotFound);
			}

		const TInt count=referencearray.Count();
		CArrayFix<TGsmSms>* smsarray=new(ELeave) CArrayFixFlat<TGsmSms>(8);
		CleanupStack::PushL(smsarray);

		for (TInt i=0; i<count; i++)
			{
			command.SetMessageNumber(referencearray[i].Reference());
			//TODO should reference be set to referencearray[i].iReference here?
			iSmsMessage->EncodeMessagePDUsL(*smsarray, reference);
			iSmsArray.AppendL(smsarray->At(0));
			smsarray->Reset();
			}
		CleanupStack::PopAndDestroy(2);  //  smsarray, referencearray
		}
	else
		{
		TBool iSentBefore=EFalse;
		TBool is16bit = EFalse;
		TBool concatenationIEPresent= iSmsMessage->SmsPDU().TextConcatenated( &is16bit );
		iSmsMessage->SmsPDU().SetTextConcatenatedL(EFalse,EFalse);

		// Fix for INC039985;
		// The cause was that resending a SMS message(single PDU, same LogId),
		// caused an assertion fault; This did not happen with an SMS with multiple PDUs.
		// The reason for that is that for the multiple PDU case the stack employes a caching mechanism
		// built in - it checks which PDUs from an SMS message have already been sent and
		// then sends only the ones which have not been sent. This is performed by using the LodId,
		// which is used to uniquely identify an SMS message and the PDUs it consists of.
		// If an SMS message consisting of multiple PDUs is resend by an application and it has been already
		// sent sucessfully, the stack deletes the previous entry corresponding to the old LogId
		// segmentation store and replaces it with the new one with a new logid.
		// This mechanism has been extended for SMS messages consisting of single PDU.
		if(!iSmsMessage->EncodeIntoSinglePDUL(iSmsArray))
			{
			if (concatenationIEPresent && is16bit)
				{
				iSmsMessage->Set16BitConcatenation(ETrue);
				}

			if(iSegmentationStore.HasEntryWithLogIdL(iSmsMessage->LogServerId(),reference,iSmsPDUData.iSent))
				{
				iSentBefore=ETrue;
				}
			else
				{
				reference=is16bit? iSegmentationStore.Next16BitReferenceL(): iSegmentationStore.Next8BitReferenceL();
				}
			iSmsMessage->EncodeMessagePDUsL(iSmsArray, reference);
			}
		else
			{
			 if( iSegmentationStore.HasEntryWithLogIdL(iSmsMessage->LogServerId(),reference,iSmsPDUData.iSent))
			 	{
			 	iSentBefore=ETrue;
			 	}
			}

		LOGSMSPROT3("CSmsMesageSend::DoSegmentMessageL [LogServerId=%d][iSentBefore=%d]",iSmsMessage->LogServerId(),iSentBefore);
		LOGSMSPROT3("CSmsMesageSend::DoSegmentMessageL [referenceNo=%d] [iCountOfSentBefore=%d ]",reference,iSmsPDUData.iSent);
		if(!iSentBefore)
			{
			iSegmentationStore.AddSubmitL(iSmsAddr,*iSmsMessage);
			}
		}
	} // CSmsMessageSend::DoSegmentMessageL


void CSmsMessageSend::CreateLogServerEvent()
	{
	LOGSMSPROT1("CSmsMessageSend::CreateLogServerEvent()");

	iState=ESmsMessageSendCreatingLogServerEvent;
	TLogId logid=(TLogId) iSmsMessage->LogServerId();

	if (logid == KLogNullId)
		{
		iSmsPDUData.iTotal=iSmsArray.Count();
		iSmsEventLogger->AddEvent(iStatus,*iSmsMessage,iSmsPDUData);
		}
	else
		{
		iSmsEventLogger->GetEvent(iStatus,logid);
		}
	SetActive();
	} // CSmsMessageSend::CreateLogServerEvent


void CSmsMessageSend::SendNextPDU()
	{
	LOGSMSPROT3("CSmsMessageSend::SendNextPDU [sending pdu %d of count=%d]",iSmsPDUData.iSent, iSmsArray.Count() );

	if (iSmsPDUData.iSent<iSmsArray.Count())
		{
		NMobilePhone::TMobileTON  ton;
		NMobilePhone::TMobileNPI  npi;

		TGsmSmsTelNumber  parsedAddress(iSmsArray[iSmsPDUData.iSent].Sca());

		iMobileSmsSendAttributesV1.iFlags = RMobileSmsMessaging::KGsmServiceCentre |
											RMobileSmsMessaging::KRemotePartyInfo  |
											RMobileSmsMessaging::KSmsDataFormat    |
											RMobileSmsMessaging::KMoreToSend;

	    //
	    // Set service centre address...
	    //
		parsedAddress.iTypeOfAddress.ConvertToETelMM(ton, npi);
		iMobileSmsSendAttributesV1.iGsmServiceCentre.iTelNumber    = parsedAddress.iTelNumber;
		iMobileSmsSendAttributesV1.iGsmServiceCentre.iTypeOfNumber = static_cast<RMobilePhone::TMobileTON>(static_cast<TInt>(ton));
		iMobileSmsSendAttributesV1.iGsmServiceCentre.iNumberPlan   = static_cast<RMobilePhone::TMobileNPI>(static_cast<TInt>(npi));

	    //
	    // Set destination address...
	    //
		iToFromTelNumber.iTypeOfAddress.ConvertToETelMM(ton, npi);
		iMobileSmsSendAttributesV1.iDestination.iTelNumber    = iToFromTelNumber.iTelNumber;
		iMobileSmsSendAttributesV1.iDestination.iTypeOfNumber = static_cast<RMobilePhone::TMobileTON>(static_cast<TInt>(ton));
		iMobileSmsSendAttributesV1.iDestination.iNumberPlan   = static_cast<RMobilePhone::TMobileNPI>(static_cast<TInt>(npi));

		iMobileSmsSendAttributesV1.iDataFormat = RMobileSmsMessaging::EFormatGsmTpdu;

		if (iSmsPDUData.iSent < iSmsArray.Count() - 1)
			{
			iMobileSmsSendAttributesV1.iMore = ETrue;
			}
		else
			{
			iMobileSmsSendAttributesV1.iMore = EFalse;
			}
			
		//
		// Debug logging of the PDU we are sending...
		//
		LOGSMSIFPDU(_L8("ETEL TX PDU: "), iSmsArray[iSmsPDUData.iSent].Pdu(), ETrue);
		LOGSMSIFSENDATTRIBUTES(_L8("SENDATTRIBS: "), iMobileSmsSendAttributesV1);
		LOGSMSIFTIMESTAMP();

		//
		// Send the message and enter the correct state...
		//
		iState = ESmsMessageSendPDU;
		iSmsMessaging.SendMessage(iStatus, iSmsArray[iSmsPDUData.iSent].Pdu(),
								  iMobileSmsSendAttributesV1Pckg);
		TimedSetActive(iSmsSettings.SendTryTimeout());
		}
	} // CSmsMessageSend::SendNextPDU


void CSmsMessageSend::UpdateSegmentationStore()
	{
	iState = ESmsMessageSendUpdatingSegmentationStore;

	TRAPD(ret,DoUpdateSegmentationStoreL());

	CompleteMyself(ret);
	} // CSmsMessageSend::UpdateSegmentationStore


void CSmsMessageSend::DoUpdateSegmentationStoreL()
	{
	LOGSMSPROT1("CSmsMessageSend::DoUpdateSegmentationStoreL()");

	iSmsPDUData.iSent++;  //  This is the end of the sequence for sending an SMS PDU
	
	if (iSmsMessage->Scheme() == EControlParametersScheme  ||
		iSmsMessage->Scheme() == ETPSRRScheme)
		{
		iSegmentationStore.AddReferenceStatusPairL(*iSmsMessage,(TInt) iMobileSmsSendAttributesV1.iMsgRef, iSegmentSequenceNumber);	
		}
	else if (iSmsMessage->Scheme() == EDefaultScheme)
		{
		iSegmentationStore.AddReferenceL(*iSmsMessage,(TInt) iMobileSmsSendAttributesV1.iMsgRef);	
		}
	else
		{
		User::Leave(KErrNotFound);	
		}
	} // CSmsMessageSend::DoUpdateSegmentationStoreL


void CSmsMessageSend::UpdateLogServerEvent()
	{
	LOGSMSPROT1("CSmsMessageSend::UpdateLogServerEvent()");

	//
	//	Fill in log event...
	//
	iState = ESmsMessageSendUpdatingLogEvent;
	iSmsPDUData.iTotal = iSmsArray.Count();
	iSmsEventLogger->ChangeEvent(iStatus, *iSmsMessage, iSmsPDUData);
	SetActive();
	} // CSmsMessageSend::UpdateLogServerEvent


void CSmsMessageSend::Complete(TInt aStatus)
	{
	LOGSMSPROT3("CSmsMessageSend::Complete [iStatus=%d, iState=%d]", iStatus.Int(), iState );

	//
	// For completion during log events, use the saved error code instead...
	//
	if (iState == ESmsMessageSendCreatingLogServerEvent  ||
		iState == ESmsMessageSendUpdatingLogEvent)
		{
		aStatus = iSendError;
		}

	//
	// Clear all PDU data that was sent...
	//
	for (TInt i=0;  i < iSmsPDUData.iSent; i++)
		{
		iSmsArray.Delete(0);
		}

	iSmsArray.Reset();
	
	iState = ESmsMessageSendIdle;

	//
	// Call the base function to perform the actual complete...
	//
	CSmsuActiveBase::Complete(aStatus);
	} // CSmsMessageSend::Complete