uifw/AvKon/src/AknEcs.cpp
author Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
Fri, 19 Feb 2010 23:04:46 +0200
branchRCL_3
changeset 4 8ca85d2f0db7
parent 0 2f259fa3e83a
child 9 aabf2c525e0f
permissions -rw-r--r--
Revision: 201003 Kit: 201007

/*
* Copyright (c) 2002-2007 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:   Support for Emergency Calls. Class Implementations.
*   Pressed keys are tracked in a buffer and CEmergencyNumberUtils
*   is used to find out if the buffer matches to emergency number.
*   Observers, if registered are called to notify them.
*
*
*/



// INCLUDES
#include <e32std.h>
#include "aknenv.h"
#include "aknappui.h"
#include "AknEcs.h"
#include <featmgr.h>
#include "AknDebug.h"

#include <CPhCltEmergencyCall.h>
#include <MPhCltEmergencyCallObserver.h>
#include <PhCltTypes.h>
#include <AvkonInternalCRKeys.h>
#include <PtiDefs.h>
#include <e32property.h>

#include "akntrace.h"

// CLASS DECLARATIONS

/** Class for getting emergency call events */
NONSHARABLE_CLASS(CPhCltEmergencyCallObserver) : public CBase,
                                                 public MPhCltEmergencyCallObserver
    {
    public:
        /** Constructor */
        CPhCltEmergencyCallObserver(CAknEcsDetector* aParent);
        
        /** Destructor */
        ~CPhCltEmergencyCallObserver( );
        
       /**
         * This method is called when client's dial request is completed.
         *
         * @param aStatus error code of the success of the operation.
         */       
        virtual void HandleEmergencyDialL( const TInt aStatus );
    private:
        /** Pointer to the parent object that really handles the events. */
        CAknEcsDetector* iParent;
    };
    
    
CPhCltEmergencyCallObserver::CPhCltEmergencyCallObserver(CAknEcsDetector* aParent)
    :iParent(aParent)
    {
    }
    
CPhCltEmergencyCallObserver::~CPhCltEmergencyCallObserver()
    {
    }
    
void CPhCltEmergencyCallObserver::HandleEmergencyDialL( const TInt /*aStatus*/ )
    {
#ifdef AVKON_RDEBUG_INFO
    _LIT(KDebugDialRequestCompleted, "Dial request is completed");
    RDebug::Print(KDebugDialRequestCompleted);
#endif
    iParent->CallAttemptedCallback( iParent );    
    }
       
/**
* Class to hold the queue of keys (TText characters) provided to the emergency call detector.
*
* Performs matching of an emergency number with the current queue.
*
* No memory is allocated after its complete construction
*
*/
NONSHARABLE_CLASS(CAknMatchingCharacterQueue) : public CBase
    {
    public:
        /**
        * These statuses are returned by MatchText
        */
        enum TStatus
            {
            ENoMatch,
            ECompleteMatch
            };
    public:
        /**
        * C++ constructor
        */
        CAknMatchingCharacterQueue( CPhCltEmergencyCall* aPhCltEmergencyCall );

        /**
        * C++ destructor
        */
        ~CAknMatchingCharacterQueue( );

        /**
        * 2nd phase constructor
        */
        void ConstructL();

        /**
        * Method to reset the buffer by resetting the pointers.
        *
        */
        void Reset();

        /**
        * Adds the passed character code to the queue. All codes append to the queue;
        * none are ignored.
        *
        * @param aNewChar New char to the queue.
        */
        void AddChar( TText aNewChar );


        /**
        * Set the entire contents of the queue. If the new buffer is longer than
        * the queue, rest are stripped.
        *
        * @param aNewBuffer New content to the queue.
        */
        void SetBuffer( const TDesC& aNewBuffer );

        /**
        * The number of characters in the queue.  Resets to 0. Increases as characters are added
        * up to and equal to KAknEcsMaxMatchingLength
        *
        * @return   the number of characters queued and available for match.
        *
        */
        TInt Count() const;

        /**
        * Returns the status of the queue (which is automatically updated when AddChar and other non-const
        * API is called)
        * @return The status of the match. Nothing matching returns ENoMatch; 
        *         The whole sequence matching returns ECompleteMatch.
        */
        TStatus Status() const;

        /**
        * Returns a pointer to the current matching number. 
        *
        * @return A ptr descriptor into the currently matching text
        */
        TPtrC CurrentMatch() const;

        /**
        * Returns an index to the current character buffer where the emergency number
        * match begins.  This value is between 0 and KAknEcsMaxMatchingLength, even if
        * there have been a large number characters input to the FIFO. Thus, it does not
        * take account of characters that have fallen out of the FIFO.
        *
        * @return the index in the current buffer where the match starts
        */
        TInt IndexOfCurrentMatch() const;

    private:
        /**
        * Update the status of the queue
        */
        void UpdateStatus( TBool aBufferMode );

    private:
        // Emergency number buffer is as long as maximum sequence
        TBuf<KAknEcsMaxMatchingLength> iCharBuffer;

        /** Pointer to phone client interface of emergency call. Not owned */
        CPhCltEmergencyCall* iPhCltEmergencyCall; 
        TStatus iStatus;      // Holds the status;
        TInt iMatchPosition;  // Position in iCharBuffer from where the match starts.

        TAny* iSpare;
    };




// CLASS IMPLEMENTATIONS

/**
 * Local Panic Function and Panic Codes
 */

enum TAknEcsPanicCodes
    {
    EAknEcsPanicDialLLeft = 1,
    EAknEcsPanicBadState
    };

GLDEF_C void Panic(TAknEcsPanicCodes aPanic)
    {
    _LIT(KPanicCat,"AknEcs");
    User::Panic(KPanicCat, aPanic);
    }

//
// Queue used for storing and matching the keys used in emergency number detection
//
//


CAknMatchingCharacterQueue::CAknMatchingCharacterQueue( CPhCltEmergencyCall* aPhCltEmergencyCall ) 
        : iPhCltEmergencyCall( aPhCltEmergencyCall )
    {
    _AKNTRACE_FUNC_ENTER;
    Reset();
    _AKNTRACE_FUNC_EXIT;
    }

CAknMatchingCharacterQueue::~CAknMatchingCharacterQueue( )
    {
    }

void CAknMatchingCharacterQueue::ConstructL()
    {
    }

void CAknMatchingCharacterQueue::Reset()
    {
    _AKNTRACE_FUNC_ENTER;
    iMatchPosition = 0;
    iCharBuffer.Zero();
    _AKNTRACE_FUNC_EXIT;
    }

void CAknMatchingCharacterQueue::AddChar( TText aNewChar )
    {    
    _AKNTRACE_FUNC_ENTER;
    _AKNTRACE( "[%s][%s] aNewChar: %s", "CAknMatchingCharacterQueue", 
        		__FUNCTION__,&aNewChar);
    
    TInt length = iCharBuffer.Length();
    TInt maxLenght = iCharBuffer.MaxLength();
    if (length >= maxLenght)
        {
        iCharBuffer = iCharBuffer.Right(length - 1);
        }
    iCharBuffer.Append( aNewChar );
    UpdateStatus( EFalse );
    _AKNTRACE_FUNC_ENTER;
    }

void CAknMatchingCharacterQueue::SetBuffer( const TDesC& aNewBuffer )
    {    
    _AKNTRACE_FUNC_ENTER;
    _AKNTRACE( "[%s][%s] aNewBuffer: %s", "CAknMatchingCharacterQueue", 
            		__FUNCTION__,&aNewBuffer);
    
    TInt maxLenght = iCharBuffer.MaxLength();
    TInt length = aNewBuffer.Length();
    if ( length > maxLenght )
        {
        length = maxLenght;
        }
    iCharBuffer = aNewBuffer.Left( length );
    UpdateStatus( ETrue );
    _AKNTRACE_FUNC_EXIT;
    }

TInt CAknMatchingCharacterQueue::Count() const
    {
    return iCharBuffer.Length();
    }

CAknMatchingCharacterQueue::TStatus CAknMatchingCharacterQueue::Status() const
    {
    return iStatus;
    }

TPtrC CAknMatchingCharacterQueue::CurrentMatch() const
    {
    return TPtrC(iCharBuffer.Right(iCharBuffer.Length() - iMatchPosition));
    }

TInt CAknMatchingCharacterQueue::IndexOfCurrentMatch() const
    {
    return iMatchPosition;
    }

void CAknMatchingCharacterQueue::UpdateStatus( TBool aBufferMode )
    {
    _AKNTRACE_FUNC_ENTER;
    TBool isEmergency = EFalse;
    TInt err = KErrNone;
    TPhCltTelephoneNumber buffer = iCharBuffer;
    if (iPhCltEmergencyCall)
        {
        err = iPhCltEmergencyCall->FindEmergencyPhoneNumber(buffer, isEmergency);        
        }
    if ( err != KErrNone )
        {
        isEmergency = EFalse;
        }

    TInt cbLength = iCharBuffer.Length();
    TInt bLength = buffer.Length();

    if ( aBufferMode && isEmergency && ( cbLength != bLength ) )
        {
        isEmergency = EFalse;
        }

    if ( isEmergency )
        {
        iMatchPosition = cbLength - bLength;
        iStatus = ECompleteMatch;
        }
    else
        {
        iMatchPosition = cbLength;
        iStatus = ENoMatch;
        }
    
    _AKNTRACE( "[%s][%s] iStatus: %d", "CAknMatchingCharacterQueue", 
                		__FUNCTION__,iStatus );
    _AKNTRACE_FUNC_EXIT;
    }


//
//
// Implementation of CAknEcsDetector
//  (Emergency Call Support Detector)
//
//

EXPORT_C CAknEcsDetector::CAknEcsDetector()
    {
    _AKNTRACE_FUNC_ENTER;
    iCoeEnv = CCoeEnv::Static();
    iState = ENotFullyConstructed;
    _AKNTRACE_FUNC_EXIT;
    }

EXPORT_C CAknEcsDetector::~CAknEcsDetector()
    {
    _AKNTRACE_FUNC_ENTER;
    // Must close this in order to remove any observers from the AppUi's monitor
    CloseEventSource();
    delete iPhCltEmergencyCall;
    delete iEmergencyCallObserver;
    delete iQueue;
    delete iIdler;
    delete iKeyTimeoutTimer;
    _AKNTRACE_FUNC_EXIT;
    }

EXPORT_C void CAknEcsDetector::ConstructL()
    {
    _AKNTRACE_FUNC_ENTER;
    iEmergencyCallObserver = new (ELeave) CPhCltEmergencyCallObserver( this );
    // Phone client interface
    iPhCltEmergencyCall = CPhCltEmergencyCall::NewL( iEmergencyCallObserver );
    iQueue = new (ELeave) CAknMatchingCharacterQueue(iPhCltEmergencyCall);
    iQueue->ConstructL();

    DetermineState();

    // Idler for delaying the change of state to Call Attempted
    iIdler = CIdle::NewL( CActive::EPriorityLow );

    // Timer for timing the timeout between keys
    iKeyTimeoutTimer = CPeriodic::NewL( CActive::EPriorityLow);
    _AKNTRACE_FUNC_EXIT;
    }

EXPORT_C CAknEcsDetector* CAknEcsDetector::NewL()
    { // static
    _AKNTRACE_FUNC_ENTER;
    CAknEcsDetector* self = new (ELeave) CAknEcsDetector();
    CleanupStack::PushL( self );
    self->ConstructL();
    CleanupStack::Pop(); //self
    _AKNTRACE_FUNC_EXIT;
    return self;
    }

EXPORT_C TBool CAknEcsDetector::ConnectToEventSource()
    {
    if ( iFlags.IsClear( EEventSourceConnected ) )
        {
        TRAPD( err, iAvkonAppUi->EventMonitor()->AddObserverL(this) );
        iFlags.Assign( EEventSourceConnected, ( err == KErrNone ) );
        return ( err == KErrNone );
        }
    else
        {
        return ETrue;
        }
    }

EXPORT_C void CAknEcsDetector::CloseEventSource()
    {
    if (iFlags.IsSet( EEventSourceConnected ) )
        {
        CAknWsEventMonitor* eventMonitor = iAvkonAppUi->EventMonitor();
        eventMonitor->RemoveObserver(this);
        iFlags.Clear( EEventSourceConnected );
        }
    }

EXPORT_C void CAknEcsDetector::HandleWsEventL(const TWsEvent& aEvent, CCoeControl* /* aDestination  */)
    {
    _AKNTRACE_FUNC_ENTER;
    _AKNTRACE( "[%s][%s] aEvent.type(): %d, aEvent.Key()->iScanCode :%d", "CAknEcsDetector", 
                            		__FUNCTION__,aEvent.Type(),aEvent.Key()->iScanCode );
    
    if ( aEvent.Type() == EEventKeyDown || 
    // EKeyPhoneEnd/EKeyNo doesn't send EEVentKeyDown events, so EEventKey is used instead
    ( ( aEvent.Key()->iScanCode == EStdKeyNo ) && ( aEvent.Type() == EEventKey ) ) 
        )
        {
        AddChar( (TText)(aEvent.Key()->iScanCode ) ); // top 16 ( in Unicode build) bits removed
        }    
    _AKNTRACE_FUNC_EXIT;
    }


EXPORT_C void CAknEcsDetector::AddChar( TText aNewChar )
    {
    _AKNTRACE_FUNC_ENTER;
    _AKNTRACE( "[%s][%s] aNewChar: %s", "CAknEcsDetector", 
                                		__FUNCTION__, &aNewChar );
    
    if (aNewChar == EKeyQwertyOn || aNewChar == EKeyQwertyOff)
    	{ 
        _AKNTRACE_FUNC_EXIT;
    	return;   // return directly if the aNewChar is slide open/closed.
    	}

    iKeyTimeoutTimer->Cancel(); // there might be pending timeout; clear it
    if ( aNewChar == EStdKeyYes || aNewChar ==EKeyPhoneSend )
        {
        if ( State() == ECompleteMatch )
            {
            _AKNTRACE( "[%s][%s] SetState( ECompleteMatchThenSendKey )", "CAknEcsDetector", 
                                            		__FUNCTION__ );
            SetState( ECompleteMatchThenSendKey );
            }
        // else do nothing with it...
        }
    else
        {
        TText scanCode = aNewChar;
#ifdef RD_INTELLIGENT_TEXT_INPUT 
        // Convert scan code to number value here 
        // for 4*10, 3*11, half-qwerty key pad
        // The convert value is referenced from KeymapBuilder.pl in ptienginev2
        // Note: if the product change the scancode defintion,pls change the following 
        // map rules.
        TInt keyboardLayout = EPtiKeyboardNone;
        TInt errCode = RProperty::Get( KCRUidAvkon, KAknKeyBoardLayout, keyboardLayout );
        if ( errCode == KErrNone &&  ( keyboardLayout == EPtiKeyboardQwerty4x10 
            || keyboardLayout == EPtiKeyboardQwerty3x11 ) )
            {
            // using same conventor rule for 3x11 & 4x10
            if ( scanCode == EPtiKeyQwertyQ )
                {
                scanCode = EPtiKeyQwerty1;
                }
            else if ( scanCode == EPtiKeyQwertyW )
                {
                scanCode = EPtiKeyQwerty2;
                }
            else if ( scanCode == EPtiKeyQwertyE )
                {
                scanCode = EPtiKeyQwerty3;
                }        
            else if ( scanCode == EPtiKeyQwertyR )
                {
                scanCode = EPtiKeyQwerty4;
                }
            else if ( scanCode == EPtiKeyQwertyT )
                {
                scanCode = EPtiKeyQwerty5;
                }
            else if ( scanCode == EPtiKeyQwertyY )
                {
                scanCode = EPtiKeyQwerty6;
                }
            else if ( scanCode == EPtiKeyQwertyU )
                {
                scanCode = EPtiKeyQwerty7;
                }
            else if ( scanCode == EPtiKeyQwertyI )
                {
                scanCode = EPtiKeyQwerty8;
                }
            else if ( scanCode == EPtiKeyQwertyO )
                {
                scanCode = EPtiKeyQwerty9;
                }
            else if ( scanCode == EPtiKeyQwertyP )
                {
                scanCode = EPtiKeyQwerty0;
                }    
            }
        else if ( errCode == KErrNone && keyboardLayout == EPtiKeyboardHalfQwerty )
            {
            if ( scanCode == EPtiKeyQwertyE )
                {
                scanCode = EPtiKeyQwerty1;
                }
            else if ( scanCode == EPtiKeyQwertyT )
                {
                scanCode = EPtiKeyQwerty2;
                }
            else if ( scanCode == EPtiKeyQwertyU )
                {
                scanCode = EPtiKeyQwerty3;
                }        
            else if ( scanCode == EPtiKeyQwertyD )
                {
                scanCode = EPtiKeyQwerty4;
                }
            else if ( scanCode == EPtiKeyQwertyG )
                {
                scanCode = EPtiKeyQwerty5;
                }
            else if ( scanCode == EPtiKeyQwertyJ )
                {
                scanCode = EPtiKeyQwerty6;
                }
            else if ( scanCode == EPtiKeyQwertyU )
                {
                scanCode = EPtiKeyQwertyC;
                }
            else if ( scanCode == EPtiKeyQwertyB )
                {
                scanCode = EPtiKeyQwerty8;
                }
            else if ( scanCode == EPtiKeyQwertyM )
                {
                scanCode = EPtiKeyQwerty9;
                }
            else if ( scanCode == EPtiKeyQwertySpace )
                {
                scanCode = EPtiKeyQwerty0;
                }    
            }
#endif   //RD_INTELLIGENT_TEXT_INPUT    
        
        iQueue->AddChar( scanCode );
        DetermineState();
        iKeyTimeoutTimer->Start( KEcsInterKeyTimeout, KEcsInterKeyTimeout, TCallBack( CancelMatch, this ) );
        }
    _AKNTRACE_FUNC_EXIT;
    }


void CAknEcsDetector::DetermineState()
    {
    _AKNTRACE_FUNC_ENTER;
    TState bestState = ENoMatch;

    if ( iQueue->Count() == 0 )
        {
        bestState = EEmpty;
        }
    else
        {
        CAknMatchingCharacterQueue::TStatus matchStatus = iQueue->Status();

        switch ( matchStatus )
            {
            case CAknMatchingCharacterQueue::ENoMatch:
                bestState = ENoMatch;
                break;
            case CAknMatchingCharacterQueue::ECompleteMatch:
                bestState = ECompleteMatch;
                break;
            default:
                __ASSERT_DEBUG( 0, Panic(EAknEcsPanicBadState) );
                break;
            }
        SetState(bestState);
        }
    _AKNTRACE( "[%s][%s] bestState: %d", "CAknEcsDetector", 
                                    		__FUNCTION__, bestState );
    _AKNTRACE_FUNC_EXIT;
    }

EXPORT_C void CAknEcsDetector::ReportEvent(TState aNewState )
    {
    if (iObserver)
        {
        iObserver->HandleEcsEvent( this, aNewState );
        }
    }


EXPORT_C TPtrC CAknEcsDetector::CurrentMatch() const
    {
    return iQueue->CurrentMatch();
    }

TInt CAknEcsDetector::IndexOfCurrentMatch() const
    {
    return iQueue->IndexOfCurrentMatch();
    }

EXPORT_C CAknEcsDetector::TState CAknEcsDetector::State()
    {
    return iState;
    }
EXPORT_C void CAknEcsDetector::SetBuffer( const TDesC& aNewBuffer )
    {
    _AKNTRACE_FUNC_ENTER;
    _AKNTRACE( "[%s][%s] aNewBuffer: %s", "CAknEcsDetector", 
                                        		__FUNCTION__, &aNewBuffer );
        
    iKeyTimeoutTimer->Cancel(); // there might be pending timeout; clear it
    iQueue->Reset();
    iQueue->SetBuffer(aNewBuffer);
    DetermineState();
    if ( State() == ECompleteMatch )
        {
        _AKNTRACE( "[%s][%s] State() == ECompleteMatch ", "CAknEcsDetector", 
                                                		__FUNCTION__ );
        iKeyTimeoutTimer->Start( KEcsInterKeyTimeout,
            KEcsInterKeyTimeout, TCallBack( CancelMatch, this ) );
        }
    _AKNTRACE_FUNC_EXIT;
    }

EXPORT_C void CAknEcsDetector::SetState( TState aNewState )
    {
    _AKNTRACE_FUNC_ENTER;
    _AKNTRACE( "[%s][%s] aNewState: %d", "CAknEcsDetector", 
                                            		__FUNCTION__, aNewState );
    ReportEvent( aNewState );

    TInt oldState = iState;
    iState = aNewState;

    switch ( oldState )
        {
        // All these are OK to go to the next state without transition action:
        case ENotFullyConstructed:
        case EEmpty:
        case ECallAttempted:
        case ENoMatch:
        case ECompleteMatchThenSendKey:
            break;

        case ECompleteMatch:
            if ( aNewState == ECompleteMatchThenSendKey )
                {
                RelinquishCapturedKeys();

                TBool proceedWithCall = ETrue;
                proceedWithCall = OfferEmergencyCall();
                if ( proceedWithCall )
                    {
                    _AKNTRACE( "[%s][%s] Attempt Emergency Call", "CAknEcsDetector", 
                                                                		__FUNCTION__);
                    AttemptEmergencyCall();
                    }
                else
                    {  // Pass through this state immediately
                    _AKNTRACE( "[%s][%s] Reset", "CAknEcsDetector", __FUNCTION__);
                    iQueue->Reset();
                    SetState( EEmpty );
                    }
                }
            break;

        default:
            break;

        }
    _AKNTRACE_FUNC_EXIT;
    }

EXPORT_C void CAknEcsDetector::Reset()
    {
    _AKNTRACE_FUNC_ENTER;
    iQueue->Reset();
    SetState( EEmpty );
    _AKNTRACE_FUNC_EXIT;
    }

/**
* This may be re-implemented to add a confirm query. Re-implementation must not leave
*
*/
EXPORT_C TBool CAknEcsDetector::OfferEmergencyCall()
    {
    return ETrue;
    }

void CAknEcsDetector::CaptureKeys()
    {
    // Capture Send Key
    RWindowGroup& groupWin=iCoeEnv->RootWin();
    iCapturedKey=groupWin.CaptureKey( EStdKeyYes, 0, 0);
    }

void CAknEcsDetector::RelinquishCapturedKeys()
    {
    RWindowGroup& groupWin=iCoeEnv->RootWin();
    groupWin.CancelCaptureKey( iCapturedKey );
    }

void CAknEcsDetector::AttemptEmergencyCall()
    {
    _AKNTRACE_FUNC_ENTER;
#ifdef AVKON_RDEBUG_INFO
    _LIT(KDebugAttemptEmergencyCall, "Attempt Emergency Call From Detector");
    RDebug::Print(KDebugAttemptEmergencyCall);
#endif

    TRAPD( err, iPhCltEmergencyCall->DialEmergencyCallL( CurrentMatch() ) );
    __ASSERT_DEBUG( err==KErrNone, Panic( EAknEcsPanicDialLLeft ) );
    if(err != KErrNone)
        {
        err = KErrNone;
        }
    _AKNTRACE_FUNC_EXIT;
    }

EXPORT_C void CAknEcsDetector::SetObserver( MAknEcsObserver* aObserver )
    {
    iObserver = aObserver;
    }

    /**
     * It is called whenever status is retrieved (by the client).
     *
     * @param aStatus It is the status of the phone.
     */
void CAknEcsDetector::HandlePhoneStatusL( const TInt /* aStatus */ )
    {
    }

    /**
    * This is meant to be called asynchronously in order for the Observer to destroy itself
    *
    */
TInt CAknEcsDetector::CallAttemptedCallback(TAny* aSelf)
    { // static
    _AKNTRACE_FUNC_ENTER;
    REINTERPRET_CAST(CAknEcsDetector*,aSelf)->SetState( ECallAttempted );
#ifdef AVKON_RDEBUG_INFO
    _LIT(KDebugCallAttemptedCallback, "CallAttemptedCallback");
    RDebug::Print(KDebugCallAttemptedCallback);
#endif
    _AKNTRACE_FUNC_EXIT;
    return 0;
    }

TInt CAknEcsDetector::CancelMatch(TAny* aThis)
    {
    _AKNTRACE_FUNC_ENTER;
    static_cast<CAknEcsDetector*>(aThis)->Reset();
    static_cast<CAknEcsDetector*>(aThis)->iKeyTimeoutTimer->Cancel();
    _AKNTRACE_FUNC_EXIT;
    return 0; // Do not repeat the operation
    }

// End of File