srsf/nssvasapi/nssvascore/src/nssvascspeechitemtrainer.cpp
author Pat Downey <patd@symbian.org>
Wed, 01 Sep 2010 12:29:17 +0100
branchRCL_3
changeset 23 e36f3802f733
parent 0 bf1d17376201
permissions -rw-r--r--
Revert incorrect RCL_3 drop: Revision: 201033 Kit: 201035

/*
* Copyright (c) 2004-2006 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:  The CNssSpeechItemTrainer provides methods to train, save
*               and delete CNssSpeechItem objects in large quantities.
*
*/


/* Here is a state machine for training:
*
* Summary:
*
* Idle state
*     |
* Waiting state     <-------------------
*     |                                | (loop back to waiting state)
* ###################################  |
* # IF NEEDED: Create lexicon state #  |
* #            |                    #  |
* # IF NEEDED: Create grammar state #---
* #            |                    #
* #       Training state            #
* ###################################
*     |
* Idle state
*
*
*
* State: Idle state
* Event: A client calls TrainTextL, 
* Action:This induces a call to CNssSpeechItemTrainer::TrainTextDealyed().
*
*        The basic idea of _delayed_training_ is that several training calls
*        are piled up before taking action. When a large number of names is
*        trained at a time, the economies of scale are considerable.
*
*        So, TrainTextDelayed() adds the name to the to-be-trained list,
*        and starts a _delay_timer_. When this delay timer has finished, the
*        actual training starts.
* Transition: Idle state -> Waiting state
*
*
* State: Waiting state
* Event: A client calls TrainTextL.
* Action:The function adds the name to the to-be-trained list, and
*        resets the timer
*        (background: In contact synchronization via PC Suite, names are added
*         every 1/2 seconds. We want to keep piling the names  as long as
*         new names keep coming. Since we can't wait 50 seconds
*         before training, the only reasonable solution is to reset the timer
*         every time a new name arrives)
* Transition: Waiting state -> Waiting state
*
*
* State: Waiting state
* Event: Timer finishes doing time.
* Action:Check the preparations for training:
*         * Has the grammar for the context been created?
*         * Has the lexicon for the context been created?
* CASE: Preconditions OK
*       Action    : Send async AddVoiceTags call
*       Transition: Waiting state -> Training state
*
* State: Training state
* Event: Training finished
* Action: Save the newly created Rule IDs to the speech items.
*         Commit the database.
*         Make the callbacks (success or failure) to the client.
*
* Transition: Check if new names were piled during traning.
*         YES: Training state -> Waiting state
*         NO : Training state -> Idle state
*
*/

#include "srsfbldvariant.hrh"
#include "nssvascspeechitemtrainer.h"
#include "nssvasctrainingparameters.h"
#include "nssvascvasdatabase.h"
#include "nssvasmsavetagclient.h"
#include "nssvasmdeletetagclient.h"

#include "rubydebug.h"

#include <badesca.h>

#ifdef _DEBUG
    // Used in UDEB only
    _LIT( KSpeechItemTrainerPanic, "VasCSpeechItemTrainer" );
#endif  // _DEBUG

#define DEBUGPANIC User::Panic( KSpeechItemTrainerPanic, __LINE__ )
#define REACT( a, b ) if ( a != KErrNone ) { b; }
#define CHECK_NOT_NULL( a ) \
    if ( !(a) ) \
        { \
        return MNssSpeechItem::EVasTrainFailed; \
        }
#define ALLOWED_STATES( a, b, c ) \
    if ( iState != a && iState != b && iState != c ) \
        { \
        return MNssSpeechItem::EVasTrainFailed; \
        } 

// tags are buffered in the buffer that grows with this granularity
const TInt KTagBufferGranularity = 100;

// rule ids are buffered in the buffer that grows with this granularity
const TInt KRuleIdBufferGranularity = 50;

// tag ids are buffered in the buffer that grows with this granularity
const TInt KRemoveTagIdBufferGranularity = 50;

// Granularity for the array of words per sinde phrase
const TInt KSindeWordArrayGranularity = 2;


// Local function declarations
TInt EnforceRuleIdCountInvariant( RArray<TUint32>& aArray, TInt aCount );

// ---------------------------------------------------------
// CNssSpeechItemTrainer::CNssSpeechItemTrainer
// C++ default constructor can NOT contain any code, that
// might leave.
// ---------------------------------------------------------
//
CNssSpeechItemTrainer::CNssSpeechItemTrainer( CNssVASDatabase* aDatabase )
: iDatabase( aDatabase ),
  iSpeechItemBuffer       ( NULL ),
  iSpeechItemTrainingBuffer( NULL ),
  iGrammarIdBuffer        ( NULL ),
  iGrammarIdDeletingBuffer( NULL ),
  iTagBuffer(NULL)
    {
    // empty
    }


// -----------------------------------------------------------------------------
// CNssSpeechItemTrainer::ConstructL
// Symbian 2nd phase constructor can leave.
// This is overloaded function for SpeechItemTrainer from database
// -----------------------------------------------------------------------------
//
void CNssSpeechItemTrainer::ConstructL()
    {
    RUBY_DEBUG_BLOCK("CNssSpeechItemTrainer::ConstructL");
    iActionTracker = CNssTrainingActionTracker::NewL( *this );

    // Create buffer for grouping phrases in training
    iSpeechItemBuffer = new(ELeave)RPointerArray<CNssSpeechItem>;
    iSpeechItemTrainingBuffer = 0; // No name being currently trained.
    }


// -----------------------------------------------------------------------------
// CNssSpeechItemTrainer::NewL
// Two-phased constructor.
// This is for new SpeechItemTrainer
// -----------------------------------------------------------------------------
//
CNssSpeechItemTrainer* CNssSpeechItemTrainer::NewL( CNssVASDatabase* aDatabase )
    {
    RUBY_DEBUG_BLOCK( "CNssSpeechItemTrainer::NewL" );

    CNssSpeechItemTrainer* self = NewLC( aDatabase );
    CleanupStack::Pop( self );
    return self;
    }


// -----------------------------------------------------------------------------
// CNssSpeechItemTrainer::NewLC
// Two-phased constructor.
// This is overloaded function for SpeechItemTrainer from database
// -----------------------------------------------------------------------------
//
CNssSpeechItemTrainer* CNssSpeechItemTrainer::NewLC( CNssVASDatabase* aDatabase )
    {
    CNssSpeechItemTrainer* self
        = new (ELeave) CNssSpeechItemTrainer( aDatabase );

    CleanupStack::PushL( self );
    self->ConstructL();
    return self;
    }

// ---------------------------------------------------------
// CNssSpeechItemTrainer::~CNssSpeechItemTrainer
// Delete the SpeechItemTrainer object and set the Portal's 
// state to Terminate.
// The Trainer does not delete the Portal, the Portal  
// deletes itself.
// ---------------------------------------------------------
//
CNssSpeechItemTrainer::~CNssSpeechItemTrainer()
    {
    RUBY_DEBUG0( "CNssSpeechItemTrainer::~CNssSpeechItemTrainer" );

    delete iActionTracker;


    // Buffer for grouping the MNssSpeechItems for efficient training.
    if ( iSpeechItemBuffer )
        {
        iSpeechItemBuffer->Reset();
        delete iSpeechItemBuffer;
        }

    // Names, which are being trained by CSISpeechRecognitionUtility.
    if ( iSpeechItemTrainingBuffer )
        {
        iSpeechItemTrainingBuffer->Reset();
        delete iSpeechItemTrainingBuffer;
        }

    // This buffer is sent to SRS Utility. Contains: The recognition phrase split into subwords.
    iPhraseArray.ResetAndDestroy();

    // Training parameters
    delete iTrainingParams;

    // Context
    delete iContext;

    // Rule ID array. Return values from AddVoiceTags() are placed here.
    if ( iRuleIDArray )
        {
        iRuleIDArray->Close();
        delete iRuleIDArray;
        }
      
    if ( iDeleteRuleIDArray )
    	{
        RUBY_DEBUG0( "CNssSpeechItemTrainer::~CNssSpeechItemTrainer Deleting iDeleteRuleIDArray" );
    	iDeleteRuleIDArray->Close();
    	delete iDeleteRuleIDArray;
    	}

    // Handle to speech services
    if ( iSrsApi )
        {
        iSrsApi->CancelUtility();
        delete iSrsApi;
        iSrsApi = NULL;
        }

    // The tags to be saved: Owned by client
    // RPointerArray<CNssTag> *iTagBuffer;
    if ( iTagBuffer )
        {
        iTagBuffer->Reset();
        delete iTagBuffer;
        iTagBuffer = 0;
        }

    if ( iGrammarIdBuffer )
        {
        iGrammarIdBuffer->Reset();
        delete iGrammarIdBuffer;
        iGrammarIdBuffer = 0;
        }

    if ( iGrammarIdDeletingBuffer )
        {
        iGrammarIdDeletingBuffer->Reset();
        delete iGrammarIdDeletingBuffer;
        iGrammarIdDeletingBuffer = 0;
        }
    }

// ---------------------------------------------------------
// CNssSpeechItemTrainer::RetrainTextDelayed
// Does delayed retraining.
// ---------------------------------------------------------
//
MNssSpeechItem::TNssSpeechItemResult CNssSpeechItemTrainer::RetrainTextDelayed(
    MNssTrainTextEventHandler* aEventHandler,
	CNssTrainingParameters*    aTrainingParams,
    CNssSpeechItem&            aSpeechItem,
    CNssContext&               aContext)
    {
    RUBY_DEBUG0( "CNssSpeechItemTrainer::RetrainTextDelayed" );

    // Check parameters: 
    // callback 
    CHECK_NOT_NULL( aEventHandler );

    // phrase to be trained
    CHECK_NOT_NULL( aSpeechItem.Text().Length() );

    // Check state
    ALLOWED_STATES( EStateIdle, ERetrainStateWaiting, ERetrainStateRetraining );

    // Check event handler
    if ( !iTrainEventHandler )
        {
        iTrainEventHandler = aEventHandler;
        }
    else if ( iTrainEventHandler != aEventHandler )
        {
        return( MNssSpeechItem::EVasTrainFailed );
        }

    // The training parameters and the context 
    // must be the same for all in the buffer.
    TRAPD( error,
        if ( !CheckTrainingParametersL( aTrainingParams ) ||
             !CheckContext( aContext ) )
            {
            return( MNssSpeechItem::EVasTrainFailed );
            }
         );
    if ( error != KErrNone )
        {
        return MNssSpeechItem::EVasTrainFailed;
        }

    // Buffer the training request

    // Allocate buffer, if not done earlier
    if ( !SpeechItemBufferNeeded() )
        {
        return( MNssSpeechItem::EVasTrainFailed );
        }

    // If this is the first tag, save Lexicon ID and Grammar ID.
    if ( iSpeechItemBuffer->Count() == 0 )
        {
        iLexiconId = aContext.LexiconId();
        iGrammarId = aContext.GrammarId();
        }
    else
        {
        // The tags after the first one must have the same Gramamr ID & Lexicon ID.
        if ( iLexiconId != aContext.LexiconId() ||
            iGrammarId != aContext.GrammarId() )
            {
            return( MNssSpeechItem::EVasTrainFailed );
            }
        }

    // Add request to buffer
    TInt ret = iSpeechItemBuffer->Append( &aSpeechItem );

    if ( ret != KErrNone )
        {
        return( MNssSpeechItem::EVasTrainFailed );
        }

    // Restart timer. When the timer has waited for a defined period, the names
    // get retrained. Timer is reseted every time RetrainTextDelayed is called.
    //
    // Retraining in groups is more efficient. The delay ensures this grouping.
    switch( iState )
        {
        case ERetrainStateRetraining:
            // Wait for the retraining to finish before acting.
            break;

        case EStateIdle:
            SetState( ERetrainStateWaiting );
            RestartTimer();
            break;

        case ERetrainStateWaiting:
            RestartTimer();
            break;

        default:
            return( MNssSpeechItem::EVasTrainFailed );
        }

    return( MNssSpeechItem::EVasErrorNone );
    }

// ---------------------------------------------------------
// CNssSpeechItemTrainer::SaveTagDelayed
// Does delayed saving.
// ---------------------------------------------------------
//
TInt CNssSpeechItemTrainer::SaveTagDelayed(
    MNssSaveTagClient* aSaveTagClient,
    CNssTag& aTag )
    {
    RUBY_DEBUG0( "CNssSpeechItemTrainer::SaveTagDelayed" );

    // Check internal state: Must not be training
    if ( this->iState != EStateIdle &&
         this->iState != ESaveStateWaiting &&
         this->iState != ESaveStateSaving )
        {
        return( KErrNotReady );
        }

    // Check arguments
    if ( aSaveTagClient == 0  )
        {
        return KErrArgument;
        }

    if ( !((CNssSpeechItem*)aTag.SpeechItem())->Trained() )
        {
        return KErrNotReady;
        }

    // The event handler must be the same for all group-saved tags
    if ( !iSaveEventHandler )
        {
        iSaveEventHandler = aSaveTagClient;
        }
    else if ( aSaveTagClient != iSaveEventHandler )
        {
        return KErrArgument;
        }

    // Buffer the saving request

    // Allocate buffer, if not done earlier
    if ( !iTagBuffer )
        {
        iTagBuffer = new RPointerArray<CNssTag>( KTagBufferGranularity );

        if ( !iTagBuffer )
            {
            return KErrNoMemory;
            }
        }

    // Add request to buffer
    TInt ret = iTagBuffer->Append( &aTag );

    if ( ret != KErrNone )
        {
        return( ret );
        }

    // Restart timer. When the timer has waited for a defined period, the names
    // get saved. Timer is reseted every time SaveTagDelayed is called.
    //
    // Saving in groups is more efficient. The delay ensures this grouping.
    switch( iState )
        {
        case ESaveStateSaving:
            // Wait for the training to finish before acting.
            break;

        case EStateIdle:
            SetState( ESaveStateWaiting );
            RestartTimer();
            break;

        case ESaveStateWaiting:
            RestartTimer();
            break;

        default:
            return( KErrCorrupt );
        }

    return( KErrNone );
    }

// ---------------------------------------------------------
// CNssSpeechItemTrainer::DeleteTagDelayed
// Does delayed deleting.
// (other items were commented in a header)
// ---------------------------------------------------------
//
TInt CNssSpeechItemTrainer::DeleteTagDelayed(
    MNssDeleteTagClient* aDeleteTagClient,
    CNssTag& aTag )
    {
    RUBY_DEBUG0( "CNssSpeechItemTrainer::DeleteTagDelayed" );

    // Check internal state: Must not be doing something else
    ALLOWED_STATES( EStateIdle, EDeleteStateWaiting, EDeleteStateDeleting );

    // Check arguments
    CHECK_NOT_NULL( aDeleteTagClient );

    // The event handler must be the same for all group-deleted tags
    if ( !iDeleteEventHandler )
        {
        iDeleteEventHandler = aDeleteTagClient;
        }
    else if ( aDeleteTagClient != iDeleteEventHandler )
        {
        return KErrArgument;
        }

    // Buffer the deleting request

    // Allocate buffer, if not done earlier
    if ( !SpeechItemBufferNeeded() ||
         !GrammarIdBufferNeeded()  )
        {
        return( MNssSpeechItem::EVasTrainFailed );
        }

    // If this is the first tag, save the grammar id.
    if ( iSpeechItemBuffer->Count() == 0 )
        {
        iGrammarId = ((CNssContext*)aTag.Context())->GrammarId();
        if ( !iDeleteRuleIDArray ) 
        	{
        	iDeleteRuleIDArray = new RArray<TUint32>;
        	if( !iDeleteRuleIDArray ) 
        		{
        		return KErrNoMemory;
	        	}
        	// no deletion. iDeleteRuleIDArray might still be used
        	}
//        delete iDeleteRuleIDArray; // if any
//        iDeleteRuleIDArray = new RArray<TUint32>;
        }
    else
        {
        // subsequent tags must have the same grammar id.
        if ( iGrammarId != ((CNssContext*)aTag.Context())->GrammarId() )
            {
            return( MNssSpeechItem::EVasTrainFailed );
            }
        }

    // Add request to buffer
    // iSpeechItemBuffer will be copied into iSpeechItemTrainingBuffer later and
    // iSpeechItemTrainingBuffer will be used to delete tags from the VAS DB
    TInt ret = iSpeechItemBuffer->Append( (CNssSpeechItem*)aTag.SpeechItem() );
    if ( ret != KErrNone )
        {
        return( MNssSpeechItem::EVasTrainFailed );
        }

    RUBY_DEBUG1( "CNssSpeechItemTrainer::DeleteTagDelayed Adding [%d] to iDeleteRuleIDArray", 
        ( ( CNssSpeechItem* )aTag.SpeechItem() )->RuleID() );

    ret = iDeleteRuleIDArray->Append( ( ( CNssSpeechItem* )aTag.SpeechItem() )->RuleID() );

    if ( ret != KErrNone )
        {
        RUBY_DEBUG1( "CNssSpeechItemTrainer::DeleteTagDelayed Adding to iDeleteRuleIDArray failed with error [%d]", ret );
        return( MNssSpeechItem::EVasTrainFailed );
        }

    // Restart timer. When the timer has waited for a defined period, the names
    // get deleted. Timer is reseted every time DeleteDagDelayed is called.
    //
    // Deleting in groups is more efficient. The delay ensures this grouping.
    switch( iState )
        {
        case EDeleteStateDeleting:
            // Wait for the training to finish before acting.
            break;

        case EStateIdle:
            SetState( EDeleteStateWaiting );
            RestartTimer();
            break;

        case EDeleteStateWaiting:
            RestartTimer();
            break;

        default:
            return( KErrCorrupt );
        }

    return( KErrNone );
    }

// ---------------------------------------------------------
// CNssSpeechItemTrainer::TrainTextDelayed
// Does delayed training.
// ---------------------------------------------------------
//
MNssSpeechItem::TNssSpeechItemResult CNssSpeechItemTrainer::TrainTextDelayed(
    MNssTrainTextEventHandler* aEventHandler,
	CNssTrainingParameters*    aTrainingParams,
    CNssSpeechItem&            aSpeechItem,
    CNssContext&               aContext)
    {
    RUBY_DEBUG0( "CNssSpeechItemTrainer::TrainTextDelayed" );

    // Check parameters: 
    // callback
    CHECK_NOT_NULL( aEventHandler );

    // Text is not null
    CHECK_NOT_NULL( aSpeechItem.Text().Length() );

    // Check state
    ALLOWED_STATES( EStateIdle, ETrainStateWaiting, ETrainStateTraining );

    // Check event handler
    if ( !iTrainEventHandler )
        {
        iTrainEventHandler = aEventHandler;
        }
    else if ( iTrainEventHandler != aEventHandler )
        {
        return( MNssSpeechItem::EVasTrainFailed );
        }

    // The training parameters and the context 
    // must be the same for all in the buffer.
    TRAPD( error,
        if ( !CheckTrainingParametersL( aTrainingParams ) ||
             !CheckContext( aContext ) )
            {
            return( MNssSpeechItem::EVasTrainFailed );
            }
        );
    if ( error != KErrNone )
        {
        return MNssSpeechItem::EVasTrainFailed;
        }

    // Buffer the training request
    if ( !SpeechItemBufferNeeded() )
        {
        return( MNssSpeechItem::EVasTrainFailed );
        }

    TInt ret = iSpeechItemBuffer->Append( &aSpeechItem );

    if ( ret == KErrNoMemory )
        {
        return( MNssSpeechItem::EVasTrainFailed );
        }

    // Restart timer. When the timer has waited for a defined period, the names
    // get trained. Timer is reseted every time TrainTextDelayed is called.
    //
    // Training in groups is more efficient. The delay ensures this grouping.
    switch( iState )
        {
        case ETrainStateTraining:
            // Wait for the training to finish before acting.
            break;

        case EStateIdle:
            SetState( ETrainStateWaiting );
            RestartTimer();
            break;

        case ETrainStateWaiting:
            RestartTimer();
            break;

        default:
            return( MNssSpeechItem::EVasTrainFailed );
        }

    return( MNssSpeechItem::EVasErrorNone );
    }

// ---------------------------------------------------------
// CNssSpeechItemTrainer::HandleTrainError
// Called if AddVoiceTags() call failed.
// ---------------------------------------------------------
//
void CNssSpeechItemTrainer::HandleTrainError( RPointerArray<CNssSpeechItem>*& anItemArray )
    {
    RUBY_DEBUG0( "CNssSpeechItemTrainer::HandleTrainError" );

    TInt count = anItemArray->Count();

    CleanUpTraining();

    // Announce the error to the client
    for( TInt k=0; k<count; k++ )
        {
        iTrainEventHandler->HandleTrainComplete( KErrGeneral );
        
        }
    iTrainEventHandler = NULL;
    // All the errors reported, everything was rolled back
    // Back tothe idle state
    SetState( EStateIdle );
    }

// ---------------------------------------------------------
// CNssSpeechItemTrainer::CleanUpTrianing
// Cleans memory allocated by training.
// ---------------------------------------------------------
//
void CNssSpeechItemTrainer::CleanUpTraining(void)
    {
    // Delete buffered names
    if ( iSpeechItemTrainingBuffer )
        {
        iSpeechItemTrainingBuffer->Close();
        }
    delete iSpeechItemTrainingBuffer;
    iSpeechItemTrainingBuffer = 0;

    // Delete parameters common to all names
    delete iTrainingParams;
    iTrainingParams = 0;

    delete iContext;
    iContext = 0;
    }

// ---------------------------------------------------------
// CNssSpeechItemTrainer::DoTrainTextDelayed
// Starts training tags. Sends the AddVoiceTags() call.
// ---------------------------------------------------------
//
void CNssSpeechItemTrainer::DoTrainTextDelayed()
    {
    SetState( ETrainStateTraining );

    // Move from speech item buffers from filling state to processing state.

    // names to be trained + names to be retrained
    iSpeechItemTrainingBuffer = iSpeechItemBuffer;
    iSpeechItemBuffer = 0;

    // Initialize iSrsApi (speech recognition utility)
    TInt error = CreateSrsApi();
    if ( error != KErrNone )
        {
        RUBY_DEBUG1( "CNssSpeechItemTrainer::DoTrainTextDelayed Creating SrsApi failed. Error [%d]", error );
        HandleTrainError( iSpeechItemTrainingBuffer );
        }
    else 
       	{
        	
	    __ASSERT_DEBUG( iContext->LexiconId() != KInvalidLexiconID, 
	                    User::Panic( KSpeechItemTrainerPanic, KErrCorrupt ) );
	    __ASSERT_DEBUG( iContext->GrammarId() != KInvalidGrammarID, 
	                    User::Panic( KSpeechItemTrainerPanic, KErrCorrupt ) );

	    // Extract texts from speech items to phrase array.

	    // iPhraseArray is an array of arrays: 
	    // 
	    //        |-- firstname1
	    //  tag1 -|-- lastname1
	    //
	    //  tag2 -|-- firstname2
	    //        |-- lastname2
	    iPhraseArray.ResetAndDestroy();

	    // Convert speech item array to the format understood by the Utility.
	    TInt ret = SpeechItems2Phrases( *iSpeechItemTrainingBuffer, iPhraseArray );
	    if ( ret != KErrNone )
	        {
	        SendTrainingCallbacks( ret );
	        /** @todo reengineer into signle return path
	            @todo check that everything is cleaned up before the return */
	        return;
	        }

	    // Initialize Rule ID array. The Utility puts the IDs
	    // of newly created rules here.
	    // Not using (ELeave) since this is non-leaving function (trapping of (ELeave) is not allowed).
	    iRuleIDArray = new RArray<TUint32>;

	    if ( iRuleIDArray == 0 )
	        {
            RUBY_DEBUG0( "CNssSpeechItemTrainer::DoTrainTextDelayed iRuleIDArray == 0" );

	        HandleTrainError( iSpeechItemTrainingBuffer );
	        }
	    else
	       	{
            RUBY_DEBUG1( "CNssSpeechItemTrainer::Calling AddVoiceTags (%d names)", 
                         iPhraseArray.Count() ); 
	       	// allocation succeeded

            TRAPD( errSinde, const RArray<RTrainingLanguageArray>& languages = 
                                        iTrainingParams->SindeLanguagesL() );
            
            if ( errSinde == KErrNone )
                {
#ifdef __SINDE_TRAINING
                TRAP_IGNORE(
                    const RArray<RTrainingLanguageArray>& languages = 
                                        iTrainingParams->SindeLanguagesL();
    		        ret = iSrsApi->AddVoiceTags( iPhraseArray,
                                                 languages,
                                                 (TSILexiconID)iContext->LexiconId(),
                                                 (TSIGrammarID)iContext->GrammarId(),
                                                 *iRuleIDArray );
                ); // TRAP_IGNORE
#endif // __SINDE_TRAINING
                }
            else
                {
                // Use the old style of training if SINDE languages are not available
		        ret = iSrsApi->AddVoiceTags( iPhraseArray,
                                             iTrainingParams->Languages(),
                                             (TSILexiconID)iContext->LexiconId(),
                                             (TSIGrammarID)iContext->GrammarId(),
                                             *iRuleIDArray );
                }

		    // error -> cleanup
		    if ( ret != KErrNone )
		        {
                RUBY_DEBUG1( "CNssSpeechItemTrainer::AddVoiceTags failed(%d)", ret );

		        HandleTrainComplete( ret );
		        }  // if ret is not KErrNone
	       	}  // if iRuleIDArray allocation succeeded
       	}  // if SRS API was created successfully
    }

// ---------------------------------------------------------
// CNssSpeechItemTrainer::DoRetrainTextDelayed
// Starts the 1st phase of retraining,
// which is removing old rules.
// ---------------------------------------------------------
//
void CNssSpeechItemTrainer::DoRetrainTextDelayed()
    {
    __ASSERT_DEBUG( iSpeechItemBuffer != 0, DEBUGPANIC );

    SetState( ERetrainStateRetraining );

    // Move speech item buffer from filling state to processing state.
    iSpeechItemTrainingBuffer = iSpeechItemBuffer;
    iSpeechItemBuffer = 0;

    // Initialize iSrsApi (speech recognition utility)
    TInt error = CreateSrsApi();
    if ( error != KErrNone )
        {
        RUBY_DEBUG1( "CNssSpeechItemTrainer::DoRetrainTextDelayed Creating SrsApi failed. Error [%d]", error );
        HandleTrainError( iSpeechItemTrainingBuffer );
        return;
        }

    // Start deleting tags
    iTagDeleteCounter = 0;
    DeleteNextTag();
    }

// ---------------------------------------------------------
// CNssSpeechItemTrainer::DoRetrainAddVoiceTags
// Phase 2 of retraining: Add voice tags
// ---------------------------------------------------------
//
void CNssSpeechItemTrainer::DoRetrainAddVoiceTags()
    {
    SetState( ERetrainStateRetraining );

    // Move from speech item buffers from filling state to processing state.

    __ASSERT_DEBUG( iContext->LexiconId() != KInvalidLexiconID, 
                    User::Panic( KSpeechItemTrainerPanic, KErrCorrupt ) );
    __ASSERT_DEBUG( iContext->GrammarId() != KInvalidGrammarID, 
                    User::Panic( KSpeechItemTrainerPanic, KErrCorrupt ) );

    // Extract texts from speech items to phrase array.

    iPhraseArray.ResetAndDestroy();

    // Convert speech item array to the format understood by the Utility.
    TInt ret = SpeechItems2Phrases( *iSpeechItemTrainingBuffer, iPhraseArray );
    if ( ret != KErrNone )
        {
        SendTrainingCallbacks( ret );
        return;
        }

    // Allocate Rule ID array. The Utility fills this array.
    // When the utility trans a name, it attaches a Rule ID to the name.
    iRuleIDArray = new RArray<TUint32>;
    if ( iRuleIDArray == 0 )
        {
        SendTrainingCallbacks( KErrNoMemory );
        return;
        }

    RUBY_DEBUG1( "CNssSpeechItemTrainer::Retrain Calling AddVoiceTags (%d names)", 
                 iPhraseArray.Count() );

    TRAPD( errSinde, const RArray<RTrainingLanguageArray>& languages = 
                                        iTrainingParams->SindeLanguagesL() );
            
    if ( errSinde == KErrNone )
        {
#ifdef __SINDE_TRAINING        	
        TRAP_IGNORE(
            const RArray<RTrainingLanguageArray>& languages = 
                                iTrainingParams->SindeLanguagesL();
	        ret = iSrsApi->AddVoiceTags( iPhraseArray,
                                         languages,
                                         (TSILexiconID)iContext->LexiconId(),
                                         (TSIGrammarID)iContext->GrammarId(),
                                         *iRuleIDArray );
        ); // TRAP_IGNORE
#endif // __SINDE_TRAINING
        }
    else
        {
        // Use the old style of training if SINDE languages are not available
        ret = iSrsApi->AddVoiceTags( iPhraseArray,
                                     iTrainingParams->Languages(),
                                     (TSILexiconID)iLexiconId,
                                     (TSIGrammarID)iGrammarId,
                                     *iRuleIDArray );
        }

    // error -> cleanup
    if ( ret != KErrNone )
        {
        RUBY_DEBUG1( "CNssSpeechItemTrainer::Retrain ERROR: AddVoiceTags(%d)", ret );

        SendTrainingCallbacks( ret );
        }

    }

// ---------------------------------------------------------
// CNssSpeechItemTrainer::DoRetrainAddVoiceTags
// After the new tags have been trained:
// Announce the new rule IDs for speech items,
// and update the rule IDs to the VAS database.
// ---------------------------------------------------------
//
void CNssSpeechItemTrainer::HandleRetrainComplete( TInt aResult )
    {
    RUBY_DEBUG1( "CNssSpeechItemTrainer::HandleTrainComplete(%d)", aResult );

    // iRuleIdArray contains the rule IDs of the newly trained tags.
    // The following call makes sure that
    // iRuleIdArray.Count() == iSpeechItemTrainingBuffer.Count().
    if ( EnforceRuleIdCountInvariant( *iRuleIDArray, 
                        iSpeechItemTrainingBuffer->Count() ) != KErrNone )
        {
        SendTrainingCallbacks( KErrNoMemory );
        }
    else
        {
        TInt count = iSpeechItemTrainingBuffer->Count();
        RArray<TNssSpeechItem> ruleIdUpdateArray( KRuleIdBufferGranularity );

        for( TInt itemIdx = 0; itemIdx < count; itemIdx++ )
            {
            CNssSpeechItem* item = (*iSpeechItemTrainingBuffer)[itemIdx];
            TUint32    ruleID = (*iRuleIDArray)[itemIdx];
            if ( ruleID != KInvalidRuleID )
                {
                // tag was trained successfully

                item->DelayedTrainingComplete( ruleID );
                
                TNssSpeechItem flatItem;

                flatItem.iRuleId = item->RuleID();
                flatItem.iTagId  = item->TagId();

                TInt ret = ruleIdUpdateArray.Append( flatItem );

                if ( ret != KErrNone )
                    {
                    ruleIdUpdateArray.Close();
                    SendTrainingCallbacks( KErrNoMemory );
                    return;
                    }
                }
            else
                {
                // training failed -> remove tag
                iDatabase->DeleteTag( item->TagId() );
                }   
            }

        aResult = iDatabase->UpdateTagRuleIDs( ruleIdUpdateArray );
        ruleIdUpdateArray.Close();
        SendTrainingCallbacks( aResult );
        }

    
    }

// ---------------------------------------------------------
// CNssSpeechItemTrainer::DoSaveTags
// Saves tags.
// ---------------------------------------------------------
//
void CNssSpeechItemTrainer::DoSaveTags()
    {

    __ASSERT_DEBUG( iState == ESaveStateWaiting, DEBUGPANIC );

    // Save the tags en masse
    TInt ret = iDatabase->SaveTags( iTagBuffer );

    // Next, we're going to make callbacks to the client.
    // Before that, we have to return the object to idle state,
    // This way, the last callback can trigger new train/save action.

    // Destroy buffered tags
    TInt count = iTagBuffer->Count();
    iTagBuffer->Close();
    delete iTagBuffer;
    iTagBuffer = 0;

    // Detach save tag client
    MNssSaveTagClient* client = iSaveEventHandler;
    iSaveEventHandler = 0;

    // Make SpeechItemTrainer ready for new challenges
    SetState( EStateIdle );

    // Make callbacks
    for ( TInt k = 0; k < count; k++ )
        {
        if ( ret == KErrNone )
            {
#ifdef _DEBUG
            TRAPD( err, client->SaveTagCompleted( KErrNone ) );
            __ASSERT_DEBUG( err == KErrNone, DEBUGPANIC );
#else
            client->SaveTagCompleted( KErrNone );
#endif // _DEBUG
            }
        else
            {
#ifdef _DEBUG
            TRAPD( err, client->SaveTagCompleted( KErrGeneral ) );
            __ASSERT_DEBUG( err == KErrNone, DEBUGPANIC );
#else
            client->SaveTagCompleted( KErrGeneral );
#endif // _DEBUG
            }
        }
    }

// ---------------------------------------------------------
// CNssSpeechItemTrainer::DoDeleteTags
// Starts tag removal.
// ---------------------------------------------------------
//
void CNssSpeechItemTrainer::DoDeleteTags()
    {
    // NOTE: This method is never called from anywhere!
    
    SetState( EDeleteStateDeleting );

    TInt err = CreateSrsApi();
    if ( err != KErrNone )
        {
        RemoveTagsFinished( err );
        return;
        }

    // Move Tag Buffer from filling state to processing state.
    iSpeechItemTrainingBuffer = iSpeechItemBuffer;
    iSpeechItemBuffer = 0;
    iSpeechItemBuffer = new RPointerArray<CNssSpeechItem>; // New filling buffer
    if( !iSpeechItemBuffer )
        {
        SendTrainingCallbacks( KErrNoMemory );
        return;
        }

    iTagDeleteCounter = 0;
    DeleteNextTag();
    }

// ---------------------------------------------------------
// CNssSpeechItemTrainer::DoDeleteGroupedTags
// Starts grouped tag removal.
// Used when deleting tags without retraining.
// ---------------------------------------------------------
//
void CNssSpeechItemTrainer::DoDeleteGroupedTags()
    {
  	RUBY_DEBUG1("CNssSpeechItemTrainer::DoDeleteGroupedTags iDeleteRuleIDArray->Count() = [%d]", iDeleteRuleIDArray->Count() );
	SetState( EDeleteStateDeleting );

    // Move Tag Buffer from filling state to processing state.
    // We want to have this buffer != NULL so that we are able to do callbacks (with appropriate error code )
    // in RemoveTagsFinished method even if e.g. CreateSrsApi fails. 
    iSpeechItemTrainingBuffer = iSpeechItemBuffer;
    iSpeechItemBuffer = 0;

    TInt err = CreateSrsApi();
    if ( err != KErrNone )
        {
        RemoveTagsFinished( err );
        return;
        }

    iSpeechItemBuffer = new RPointerArray<CNssSpeechItem>; // New filling buffer
    
    if( !iSpeechItemBuffer )
        {
        // As this method is not used when retraining, no need 
        // to send other callbacks than those related to deleting tags.
        RemoveTagsFinished( KErrNoMemory );
        return;
        }

	TInt ret = iSrsApi->RemoveRules( ( TSIGrammarID )iGrammarId,
                                     *iDeleteRuleIDArray );

    if ( ret != KErrNone && ret != KErrNotFound )
        {
        RUBY_DEBUG1( "CNssSpeechItemTrainer::DoDeleteGroupedTags RemoveRules failed. \
                      Error [%d]", ret );
        RemoveTagsFinished( ret );
        return;
        }

    }

// ---------------------------------------------------------
// CNssSpeechItemTrainer::DeleteNextTag
// Sends a RemoveRule() call for the next rule in the removal queue.
// ---------------------------------------------------------
//
void CNssSpeechItemTrainer::DeleteNextTag()
    {
    RUBY_DEBUG2( "CNssSpeechItemTrainer::DeleteNextTag iTagDeleteCounter [%d], iSpeechItemTrainingBuffer->Count() [%d]", iTagDeleteCounter, iSpeechItemTrainingBuffer->Count() );

    __ASSERT_DEBUG( iTagDeleteCounter <= iSpeechItemTrainingBuffer->Count(), 
                    User::Panic( KSpeechItemTrainerPanic, __LINE__ ) );

    CNssSpeechItem* speechItem = (*iSpeechItemTrainingBuffer)[iTagDeleteCounter];

    TInt ret = iSrsApi->RemoveRule(
        (TSIGrammarID)speechItem->GrammarId(),
        (TSIRuleID)speechItem->RuleID()
        );

    if ( ret != KErrNone && ret != KErrNotFound )
        {
        RemoveTagsFinished( ret );
        return;
        }
    }


// ---------------------------------------------------------
// CNssSpeechItemTrainer::HandleRemoveTagComplete
// Called after SRS has successfully removed a tag.
// ---------------------------------------------------------
//
void CNssSpeechItemTrainer::HandleRemoveTagComplete()
    {
    iTagDeleteCounter++;

    if ( iTagDeleteCounter < iSpeechItemTrainingBuffer->Count() )
        {
        DeleteNextTag();
        }
    else
        {
        RemoveTagsFinished( KErrNone );
        return;
        }
    }
    
// ---------------------------------------------------------
// CNssSpeechItemTrainer::HandleRemoveTagsComplete
// Called after SRS has successfully removed a group of tags.
// ---------------------------------------------------------
//
void CNssSpeechItemTrainer::HandleRemoveTagsComplete()
    {
    RemoveTagsFinished( KErrNone );
    }

// ---------------------------------------------------------
// CNssSpeechItemTrainer::HandleRemoveTagFailed
// Called if SRS fails to remove a tag.
// ---------------------------------------------------------
//
void CNssSpeechItemTrainer::HandleRemoveTagFailed( TInt aError )
    {
    RemoveTagsFinished( aError );
    }

// ---------------------------------------------------------
// CNssSpeechItemTrainer::RemoveTagsFinished
// All required tags have been removed.
// Go to next phase.
//    functions a bit defferently depending on whether we were retraining or
//    deleting tags
// ---------------------------------------------------------
//
void CNssSpeechItemTrainer::RemoveTagsFinished( TInt aSuccess )
    {
    // The continuation depends on what we are doing:

    // If we are deleting tags
    if ( iState == EDeleteStateDeleting )
        {
        if ( aSuccess == KErrNone )
            {
            RemoveTagsFromVAS();
            }
        else{
            FinishDeleteTags( aSuccess );
            }
        }

    // If we are retraining 
    // (removing the old rules before adding the new ones)
    else if ( iState == ERetrainStateRetraining )
        {
        if  ( aSuccess == KErrNone )
            {
            DoRetrainAddVoiceTags();
            }
        else{
            SendTrainingCallbacks( aSuccess );
            }
        }
    else
        {
        RUBY_DEBUG1( "CNssSpeechItemTrainer::RemoveTagsFinished - ERROR: Wrong state(%d)", iState );
        }
    }

// ---------------------------------------------------------
// CNssSpeechItemTrainer::RemoveTagsFromVAS
// Deletes tags from VAS.
// ---------------------------------------------------------
//
void CNssSpeechItemTrainer::RemoveTagsFromVAS()
    {
    RArray<TUint32> tagIdArray( KRemoveTagIdBufferGranularity );
    TInt ret = KErrNone;

    ////////////// Remove tags from VAS DB ///////////////////

    // Take the Tag IDs to an array.
    for ( TInt k( 0 ); k < iSpeechItemTrainingBuffer->Count(); k++ )
        {
        ret |= tagIdArray.Append( (*iSpeechItemTrainingBuffer)[k]->TagId() );
        }

    // Delete tags from VAS DB
    if ( ret == KErrNone )
        {
        ret = iDatabase->DeleteTags( tagIdArray );
        }

    // Close tag ID array.
    tagIdArray.Close();

    ////////////// Remove tags from VAS DB done //////////////

    FinishDeleteTags( KErrNone );
    }

// ---------------------------------------------------------
// CNssSpeechItemTrainer::FinishDeleteTags
// Cleans up and sends calbacks after
// deleting has finished or completed.
// ---------------------------------------------------------
//
void CNssSpeechItemTrainer::FinishDeleteTags( TInt aSuccess )
    {
    RUBY_DEBUG1("CNssSpeechItemTrainer::FinishDeleteTags aSuccess [%d]", aSuccess);
    // Commit or uncommit according to success
    if ( iSrsApi )
        {
        if ( aSuccess == KErrNone )
            {
            CommitSIUpdate();
            }
        else{
            UncommitSIUpdate();
            }
        }
	
    // Free memory
    TInt count( 0 );
    if ( iSpeechItemTrainingBuffer )
        {        
        count = iSpeechItemTrainingBuffer->Count();
        RUBY_DEBUG1("CNssSpeechItemTrainer::FinishDeleteTags aSuccess Freeing [%d] items from the training buffer", count);
        iSpeechItemTrainingBuffer->Close();
        }
    delete iSpeechItemTrainingBuffer;
    iSpeechItemTrainingBuffer = 0;

    // Send callbacks
    MNssDeleteTagClient* client = iDeleteEventHandler;
    iDeleteEventHandler = 0;
    iTagDeleteCounter = 0;

    
    for ( TInt k( 0 ); k < count; k++ )
        {
        RUBY_DEBUG1("CNssSpeechItemTrainer::FinishDeleteTags sending callback for item [%d]", k);
        // Failed or succeedeed, one rule is processed
        iDeleteRuleIDArray->Remove(0);
       
        client->DeleteTagCompleted( aSuccess );
        }
    RUBY_DEBUG2("CNssSpeechItemTrainer::FinishDeleteTags Reported [%d] deletions. [%d] remaining. Setting idle state", count, iDeleteRuleIDArray->Count());
    SetState( EStateIdle );

    }

// ---------------------------------------------------------
// CNssSpeechItemTrainer::RunBufferedActionsL
// Called when it is time to execute the buffered actions
// ---------------------------------------------------------
//
void CNssSpeechItemTrainer::RunBufferedActionsL()
    {
    RUBY_DEBUG_BLOCK( "CNssSpeechItemTrainer::RunL" );
    
    if ( iState == ETrainStateWaiting )
        {
        DoTrainTextDelayed();
        }
    else if ( iState == ESaveStateWaiting )
        {
        DoSaveTags();
        }
    else if ( iState == EDeleteStateWaiting )
        {
        DoDeleteGroupedTags();
        }
    else if ( iState == ERetrainStateWaiting )
        {
        DoRetrainTextDelayed();
        }
    else
        {
        RUBY_DEBUG1( "CNssSpeechItemTrainer::RunL - ERROR: Wrong state (%d)", iState );
        }
    }

// ---------------------------------------------------------
// CNssSpeechItemTrainer::RestartTimer
// Restarts the timer.
// ---------------------------------------------------------
//
void CNssSpeechItemTrainer::RestartTimer()
    {
    TRAP_IGNORE( iActionTracker->ActionRequestedL() );
    }

// ---------------------------------------------------------
// CNssSpeechItemTrainer::CheckTrainingParametersL
// All tags in the queue should have the same training parameters. This
// function check that a new context has similar training parameters
// as the previous ones.
// ---------------------------------------------------------
//
TBool CNssSpeechItemTrainer::CheckTrainingParametersL( const CNssTrainingParameters* aParams )
    {
    if ( aParams )
        {
        if ( !iTrainingParams )
            {
            // Make a local copy of training parameters
            iTrainingParams = CNssTrainingParameters::NewL();

            const RArray<TLanguage>& paramLangArray = aParams->Languages();

            RArray<TLanguage>* langArray = new ( ELeave ) RArray<TLanguage>;
            CleanupStack::PushL( langArray );
                
            for ( TInt k = 0; k < paramLangArray.Count(); k++ )
                {
                TInt ret = langArray->Append( paramLangArray[ k ] );
                if ( ret != KErrNone )
                    {
                    langArray->Close();
                    delete langArray;
                    return EFalse;
                    }
                }
                
            // Copy training languages
            iTrainingParams->SetLanguages( langArray );
            CleanupStack::Pop( langArray );

            // Copy SINDE languages
            TRAP_IGNORE( iTrainingParams->SetSindeLanguages( aParams->SindeLanguagesL() ) );
            
            // Copy separator
            iTrainingParams->SetSeparator( aParams->Separator() );
            }
        else
            {
            // Compare with previous training parameters
            
            // Separator
            if ( aParams->Separator() != iTrainingParams->Separator() )
                {
                return EFalse;
                }

            // Training languages
            for ( TInt k = 0; k < aParams->Languages().Count(); k++ )
                {
                if ( aParams->Languages()[ k ] != iTrainingParams->Languages()[ k ] )
                    {
                    return EFalse;
                    }
                }
            
            // @todo SINDE languages should be checked also!            
            }
       }
       
    return ETrue;
    }

// ---------------------------------------------------------
// CNssSpeechItemTrainer::CheckContext
// All tags in the queue should have the same contexts. This
// function check that a new context is similar to that of previous tags.
// ---------------------------------------------------------
//
TBool CNssSpeechItemTrainer::CheckContext(CNssContext& aContext)
    {
    if ( &aContext == 0 ) // Default settings - anything goes.
        {
        return( EFalse );
        }

    if ( aContext.RecognitionMode() != ENSSSdSiMode &&
         aContext.RecognitionMode() != ENSSSiMode )
        {
        return( EFalse );
        }

    if ( iContext )
        {
        // Check that identifiers (name & ID) are identical.
        if ( aContext.ContextId() != iContext->ContextId() )
            {
            return( EFalse );
            }

        if ( aContext.ContextName().Compare( iContext->ContextName() ) != 0 )
            {
            return( EFalse );
            }
        }
    else
        {
        if ( aContext.RecognitionMode()  == ENSSSdSiMode )
            {
            aContext.SetRecognitionMode( ENSSSiMode );
            }

        TRAPD( error, iContext = aContext.CopyL() );
        if ( error != KErrNone )
            {
            return EFalse;
            }
        }

    return( ETrue );
    }

// ---------------------------------------------------------
// CNssSpeechItemTrainer::HandleTrainComplete
// Called after speech recognition utility has trained all tags.
// Announces the newly assigned Rule IDs for the speech items.
// Signals the VAS client that training has been finished.
// ---------------------------------------------------------
//
void CNssSpeechItemTrainer::HandleTrainComplete( TInt aResult )
    {
    RUBY_DEBUG1( "CNssSpeechItemTrainer::HandleTrainComplete(%d)", aResult );

    // iRuleIdArray contains the rule IDs of the newly trained tags.
    // The following call makes sure that
    // iRuleIdArray.Count() == iSpeechItemTrainingBuffer.Count().
    if ( EnforceRuleIdCountInvariant( *iRuleIDArray, 
              iSpeechItemTrainingBuffer->Count() ) != KErrNone )
        {
        SendTrainingCallbacks( KErrNoMemory );
        }

    // Save rule IDs. Some rule IDs may have "KInvalidRuleID". This is a sign
    // that training failed. It's good to save also that info to speech items.
    for( TInt itemIdx = 0; itemIdx < iSpeechItemTrainingBuffer->Count(); itemIdx++ )
        {
        CNssSpeechItem* item = (*iSpeechItemTrainingBuffer)[ itemIdx ];
        TUint32    ruleID = (*iRuleIDArray)[ itemIdx ];

        item->DelayedTrainingComplete( ruleID );
        }

    SendTrainingCallbacks( aResult );
    }

// ---------------------------------------------------------
// CNssSpeechItemTrainer::SendTrainingCallbacks
// 
// ---------------------------------------------------------
//
void CNssSpeechItemTrainer::SendTrainingCallbacks( TInt aResult )
    {
    // If the changes were successful, commit them.
    // If they werent, roll back the changes.
    if ( aResult == KErrNone )
        {
        CommitSIUpdate();
        }
    else{
        UncommitSIUpdate();
        }

    // Save count (callback count)...
    TInt count = iSpeechItemTrainingBuffer->Count();
    MNssTrainTextEventHandler* callback = iTrainEventHandler;
    RArray<TUint32>* ruleIdArray = iRuleIDArray;
    iRuleIDArray = 0;

    // Delete buffered names that were just trained
    if ( iSpeechItemTrainingBuffer )
        {
        iSpeechItemTrainingBuffer->Close();
        }
    delete iSpeechItemTrainingBuffer;
    iSpeechItemTrainingBuffer = 0;


    // If there are names on the training queue,
    // restart the waiting of delayed training.
    if ( iSpeechItemBuffer && iSpeechItemBuffer->Count() > 0 )
        {
        // don't delete context etc as training will be continued
        SetState( ETrainStateWaiting );
        RestartTimer();
        }
    else
        {
        SetState( EStateIdle );
            
        // Cleanup
        delete iTrainingParams;
        iTrainingParams = 0;

        delete iContext;
        iContext = 0;
        
        iTrainEventHandler = 0;        
        }

    // Make the callbacks to the client according to the recognition result.
    for( TInt k( 0 ); k < count; k++ )
        {
        if ( ruleIdArray->Count() <= k )
            {
            RUBY_DEBUG2( "CNssSpeechItemTrainer::SendTrainingCallbacks ruleIdArray->Count() [%d] <= k [%d]", ruleIdArray->Count(), k );
            callback->HandleTrainComplete( KErrNoMemory );
            }
        if ( (*ruleIdArray)[ k ] == KInvalidRuleID )
            {
            RUBY_DEBUG0( "CNssSpeechItemTrainer::SendTrainingCallbacks (*ruleIdArray)[ k ] == KInvalidRuleID" );
            if ( aResult == KErrNone )
                {
                callback->HandleTrainComplete( KErrGeneral );
                }
            else
                {
                callback->HandleTrainComplete( aResult );
                }
            
            }
        else
            {
            callback->HandleTrainComplete( KErrNone );
            }
        }

    ruleIdArray->Close();
    delete ruleIdArray;
    ruleIdArray = 0;
    }

// ---------------------------------------------------------
// CNssSpeechItemTrainer::MsruoEvent
// SRS Utility callback function 
// ---------------------------------------------------------
//
void CNssSpeechItemTrainer::MsruoEvent( TUid aEvent, TInt aResult )
    {
    switch( aEvent.iUid )
        {
        case KUidAsrEventAddVoiceTagsVal:
            RUBY_DEBUG0( "CNssSpeechItemTrainer::MsruoEvent - state is KUidAsrEventAddVoiceTagsVal" );

            if ( iState == ETrainStateTraining )
                {
                HandleTrainComplete( aResult );
                }
            else if ( iState == ERetrainStateRetraining )
                {
                HandleRetrainComplete( aResult );
                }
            break;

        case KUidAsrEventRemoveRuleVal:
            RUBY_DEBUG0( "CNssSpeechItemTrainer::MsruoEvent - state is KUidAsrEventRemoveRuleVal" );

            if ( aResult == KErrNone || aResult == KErrNotFound )
                {
                HandleRemoveTagComplete();
                }
            else{
                HandleRemoveTagFailed( aResult );
                }
            break;
            
        case KUidAsrEventRemoveRulesVal:
            RUBY_DEBUG0( "CNssSpeechItemTrainer::MsruoEvent - state is KUidAsrEventRemoveRulesVal" );

        	if( aResult == KErrNone || aResult == KErrNotFound )
        		{
        		HandleRemoveTagsComplete();
        		}
        	else 
        		{
        		HandleRemoveTagFailed( aResult );
        		}
        	break;

        default:
            RUBY_DEBUG2( "CNssSpeechItemTrainer::MsruoEvent - ERROR: Uknown state (%d,%d)", aEvent.iUid, aResult );
            break;
        }
    }

// ---------------------------------------------------------
// CNssSpeechItemTrainer::SplitPhraseSindeL
// Splits a phrase to parts. Returns the parts in array.
// ---------------------------------------------------------
//
CDesC16ArrayFlat* CNssSpeechItemTrainer::SplitPhraseSindeL( const TDesC& aPhrase, 
                                                            TChar aSeparator )
    {
    CDesC16ArrayFlat* wordArray = new ( ELeave ) CDesC16ArrayFlat( KSindeWordArrayGranularity );
    CleanupStack::PushL( wordArray );

    // Separate the words in the phrase.
    TPtrC text = aPhrase; // Cursor to the phrase

    do
        {
        TInt index = text.Locate( aSeparator );

        if ( index != 0 )
            {
            // no more separators -> this is the last word -> end of word == end of string
            if ( index == KErrNotFound ) 
                {
                index = text.Length();
                }

                // Add word to array
                wordArray->AppendL( text.Left( index ) );
                }

        // Discard the processed word
        TInt charactersLeft = text.Length() - index; 
        // Forget the separator
        if ( charactersLeft > 0 )                    
            {
            charactersLeft--;                        
            }

        // Update text cursor
        text.Set( text.Right( charactersLeft ) );

        } 
    while( text.Length() > 0 );

    CleanupStack::Pop( wordArray );
    return wordArray;
    }

// ---------------------------------------------------------
// CNssSpeechItemTrainer::SplitPhrase
// Splits a phrase to parts. Returns the parts in array.
// ---------------------------------------------------------
//
CDesC16ArrayFlat* CNssSpeechItemTrainer::SplitPhraseL( const TDesC& aPhrase, TChar aSeparator )
    {
    CDesC16ArrayFlat *wordArray = new (ELeave) CDesC16ArrayFlat( KSindeWordArrayGranularity );

    // Separate the words in the phrase. Especially firstname / lastname.
    TPtrC text = aPhrase; // Cursor to the phrase

    do
        {
        TInt index = text.Locate( aSeparator );

        // no more separators -> this is the last word -> end of word == end of string
        if ( index == -1 ) 
            {
            index = text.Length();
            }

        // Add word to array
        wordArray->AppendL( text.Left( index ) );

        // Discard the processed word
        TInt charactersLeft = text.Length() - index; 
        // Forget the separator
        // (the last word doesn't have one)
        if ( charactersLeft > 0 )                    
            {
            charactersLeft--;                        
            }

        // Update text cursor
        text.Set( text.Right( charactersLeft ) );

        } 
    while( text.Length() > 0 );

    return wordArray;
    }

// ---------------------------------------------------------
// CNssSpeechItemTrainer::CommitSIUpdate
// Commits changes to SRS and destroys SRS utility.
// ---------------------------------------------------------
//
TInt CNssSpeechItemTrainer::CommitSIUpdate()
    {
    if ( !iSrsApi )
        {
        return( KErrNotReady );
        }

    iSrsApi->CommitChanges();

    delete iSrsApi;
    iSrsApi = NULL;

    return( KErrNone );
    }

// ---------------------------------------------------------
// CNssSpeechItemTrainer::UncommitSIUpdate
// Destroys SRS utility without committing.
// ---------------------------------------------------------
//
TInt CNssSpeechItemTrainer::UncommitSIUpdate()
    {
    if ( !iSrsApi )
        {
        return( KErrNotReady );
        }

    delete iSrsApi;
    iSrsApi = NULL;

    return( KErrNone );
    }

// ---------------------------------------------------------
// CNssSpeechItemTrainer::SpeechItemBufferNeeded
// Trys to allocate a speech item buffer
// ---------------------------------------------------------
//
TBool CNssSpeechItemTrainer::SpeechItemBufferNeeded()
    {
    if ( !iSpeechItemBuffer )
        {
        iSpeechItemBuffer = new RPointerArray<CNssSpeechItem>;
        if ( !iSpeechItemBuffer )
            {
            return( EFalse );
            }
        }

    return( ETrue );
    }

// ---------------------------------------------------------
// CNssSpeechItemTrainer::SpeechItemBufferNeeded
// Trys to allocate a grammar id buffer
// ---------------------------------------------------------
//
TBool CNssSpeechItemTrainer::GrammarIdBufferNeeded()
    {
    if ( !iGrammarIdBuffer )
        {
        iGrammarIdBuffer = new RArray<TUint32>;
        if ( !iGrammarIdBuffer )
            {
            return( EFalse );
            }
        }

    return( ETrue );
    }

// ---------------------------------------------------------
// CNssSpeechItemTrainer::SpeechItemBufferNeeded
// Trys to create SRS api. Returns success status.
// ---------------------------------------------------------
//
TInt CNssSpeechItemTrainer::CreateSrsApi()
    {
    if ( !iSrsApi )
        {
        TRAPD( err, iSrsApi = CNssSiUtilityWrapper::NewL( *this, KNssVASApiUid ) );
        REACT( err, return( err ) );

        iSrsApi->SetEventHandler( this );
        }

    return KErrNone;
    }


// ---------------------------------------------------------
// CNssSpeechItemTrainer::SpeechItems2Phrases
// Converts an array of speech items
// into Utility's AddVoiceTags array format.
// ---------------------------------------------------------
//
TInt CNssSpeechItemTrainer::SpeechItems2Phrases(
    RPointerArray<CNssSpeechItem>& aSpeechItems,
    RPointerArray<MDesCArray>& aPhrases )
    {
    // iPhraseArray is an array of arrays: 
    // 
    //        |-- firstname1
    //  tag1 -|-- lastname1
    //
    //  tag2 -|-- firstname2
    //        |-- lastname2

    // Create training parameters, if none were given
    if ( iTrainingParams == 0 )
        {
        RArray<TLanguage>* languages = new RArray<TLanguage>();

        if ( !languages )
            {
            return( KErrNoMemory );
            }

        // Use UI language + 1 other as default if languages are not given
        // in training parameters
        TInt err = languages->Append( User::Language() );

        if ( err == KErrNone )
            {
            err = languages->Append( ELangOther );
            }

        if ( err != KErrNone )
            {
            languages->Close();
            delete languages;
            return( err );
            }

        TRAP( err, iTrainingParams = CNssTrainingParameters::NewL() ); 
        if ( err != KErrNone )
            {
            languages->Close();
            delete languages;
            return( err );
            }

        iTrainingParams->SetLanguages( languages );
        }

    // Split the phrases to subwords (names -> first name + last name)
    for ( TInt k( 0 ); k < aSpeechItems.Count(); k++ )
        {
        // Split names into the first name and the last name
        CDesC16ArrayFlat *wordArray = 0;

        // Check if SINDE type of training should be used
        TRAPD( errSinde, const RArray<RTrainingLanguageArray>& temp = 
                                        iTrainingParams->SindeLanguagesL() );
        if ( errSinde == KErrNone )
            {
            TRAP_IGNORE( wordArray = SplitPhraseSindeL( aSpeechItems[ k ]->RawText(),
                                                        iTrainingParams->Separator() ) );
            }
        else
            {
            TRAP_IGNORE( wordArray = SplitPhraseL( aSpeechItems[ k ]->Text(),
                                                   iTrainingParams->Separator() ) );
            }

        if ( wordArray == 0 )
            {
            return KErrNoMemory;
            }

        TInt ret = aPhrases.Append( wordArray );

        if ( ret != KErrNone )
            {
            return ret;
            }
        }

    return( KErrNone );
    }
    
// ---------------------------------------------------------
// CNssSpeechItemTrainer::SetState
// Changes state of the trainer
// ---------------------------------------------------------
//
void CNssSpeechItemTrainer::SetState(TTrainState aState) 
	{
	RUBY_DEBUG2("CNssSpeechItemTrainer::SetState Switching state from [%d] to [%d]", iState, aState);
#ifdef _DEBUG
	if( iState == EDeleteStateWaiting ) 
		{
		RUBY_DEBUG0("CNssSpeechItemTrainer::SetState From EDeleteStateWaiting");
		}
	if( aState == EDeleteStateWaiting ) 
		{
		RUBY_DEBUG0("CNssSpeechItemTrainer::SetState To EDeleteStateWaiting");
		}
		
#endif	
	iState = aState;
	}

// ---------------------------------------------------------
// CNssSpeechItemTrainer::EnforceRuleIdCountInvariant
// Adds KInvalidRuleId to the Rule ID Array, until
// the number of rules is equal to count.
// ---------------------------------------------------------
//
TInt EnforceRuleIdCountInvariant( RArray<TUint32>& aArray, TInt aCount )
    {
    while( aArray.Count() < aCount )
        {
        TInt err = aArray.Append( KInvalidRuleID );
        if ( err != KErrNone )
            {
            return( err );
            }
        }

    return KErrNone;
    }