/*
* Copyright (c) 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:  Vibrate plugin interface implementation
*
*/


// INCLUDE FILES
#include <avkon.hrh>
#include <AknQueryDialog.h>
#include <aknmessagequerydialog.h>
#include <DiagFrameworkDebug.h>         
#include <DiagTestExecParam.h>      
#include <DiagTestObserver.h>       
#include <devdiagvibratepluginrsc.rsg>
#include <hal.h>
#include <hwrmpowerstatesdkpskeys.h>
#include <MProEngEngine.h>
#include <MProEngProfile.h>
#include <MProEngToneSettings.h>
#include <ProEngFactory.h> 
#include <DiagEngineCommon.h>    
#include <DiagCommonDialog.h>    
#include <StringLoader.h>           
#include <centralrepository.h>

#include "diagvibrateplugin.h"
#include "diagvibrateplugin.hrh"
#include "diagvibratepluginengine.h"
#include "diagvibratepluginprivatecrkeys.h"

// EXTERNAL DATA STRUCTURES

// EXTERNAL FUNCTION PROTOTYPES

// CONSTANTS
const TUid KDiagVibratePluginUid = { DIAG_VIBRATE_PLUGIN_UID };
static const TInt KProgressDialogIncrementSize = 10;
static const TInt KProgressDialogFinalValue = 100;
static const TInt KHundredthsInSecond = 100;

static const TInt KVibratePluginTotalStep = 1;

// MACROS
_LIT( KDiagVibratePluginResourceFileName, "z:DevDiagVibratePluginRsc.rsc" );

// LOCAL CONSTANTS AND MACROS
#ifdef _DEBUG
    enum TVibratePluginPanic
        {
        EPanicUnknownVibraState= 900,
        EPanicInitParamsNull,
        EPanicStateRepeated
        };

    _LIT( KVibratePluginErrorUnknownVibraStateText, "VibraStatusChanged: Unknown vibra state" );
    _LIT( KVibratePluginErrorInitParamsNullText, "NewL: InitParams NULL" );
    _LIT( KVibratePluginErrorStateRepeatedText, "State change not effective" );
#endif

// MODULE DATA STRUCTURES

// LOCAL FUNCTION PROTOTYPES

// FORWARD DECLARATIONS

// ============================= LOCAL FUNCTIONS ==============================

// ========================= MEMBER FUNCTIONS ================================

// ---------------------------------------------------------------------------
// CDiagVibratePlugin::NewL()
//
// Symbian OS default constructor
// ---------------------------------------------------------------------------
//
MDiagPlugin* CDiagVibratePlugin::NewL( TAny* aInitParams )
    {
    __ASSERT_DEBUG( aInitParams, User::Panic ( KVibratePluginErrorInitParamsNullText, 
        EPanicInitParamsNull ));

    CDiagPluginConstructionParam* param = NULL;
    param = static_cast<CDiagPluginConstructionParam*>( aInitParams );    

    CleanupStack::PushL( param );    
    CDiagVibratePlugin* self = new( ELeave ) CDiagVibratePlugin ( param );
    CleanupStack::Pop( param );

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


// ----------------------------------------------------------------------------
// CDiagVibratePlugin::~CDiagVibratePlugin
//
// Destructor
// ----------------------------------------------------------------------------
//
CDiagVibratePlugin::~CDiagVibratePlugin()
    {
    Cancel();
    delete iProgressDialog;
    delete iEngine;
    }


// ---------------------------------------------------------------------------
// From MDiagPlugin
// CDiagVibratePlugin::IsVisible()
// ---------------------------------------------------------------------------
//
TBool CDiagVibratePlugin::IsVisible() const
    {
    return ETrue;
    }


// ---------------------------------------------------------------------------
// From MDiagTestPlugin
// CDiagVibratePlugin::RunMode()
// ---------------------------------------------------------------------------
//
MDiagTestPlugin::TRunMode CDiagVibratePlugin::RunMode() const
    {
    return EInteractiveDialog;
    }


// ---------------------------------------------------------------------------
// From MDiagTestPlugin
// CDiagVibratePlugin::TotalSteps()
// ---------------------------------------------------------------------------
//
TUint CDiagVibratePlugin::TotalSteps() const
    {
    return KVibratePluginTotalStep;
    }


// ---------------------------------------------------------------------------
// From MDiagPlugin
// CDiagVibratePlugin::GetPluginNameL
// ---------------------------------------------------------------------------
//
HBufC* CDiagVibratePlugin::GetPluginNameL( TNameLayoutType aLayoutType ) const
    {
    switch ( aLayoutType )
        {
        case ENameLayoutHeadingPane:
            return StringLoader::LoadL( R_DIAG_MSG_TITLE_VIBRATE );

        case ENameLayoutPopupInfoPane:
            return StringLoader::LoadL ( R_DIAG_MSG_INFO_VIBRATE );

        case ENameLayoutTitlePane:
            return StringLoader::LoadL( R_DIAG_TITLE_TEST_VIBRATE );

        case ENameLayoutListSingleGraphic:
            return StringLoader::LoadL( R_DIAG_LST_TEST_VIBRATE );

        case ENameLayoutListSingle:
            return StringLoader::LoadL ( R_DIAG_LST_VIBRATE );

        default:
            LOGSTRING2( "CDiagVibratePlugin::GetPluginNameL: "
                L"ERROR: Unsupported layout type %d", aLayoutType )
            __ASSERT_DEBUG( 0, User::Invariant() );
            return StringLoader::LoadL( R_DIAG_LST_VIBRATE );
        }
    }

// ---------------------------------------------------------------------------
// From MDiagPlugin
// CDiagVibratePlugin::Uid
// ---------------------------------------------------------------------------
//
TUid CDiagVibratePlugin::Uid() const
    {
    return KDiagVibratePluginUid;
    }


// ---------------------------------------------------------------------------
// From MDiagVibratePluginObserver
// MDiagVibratePluginObserver::VibraStatusChanged
// ---------------------------------------------------------------------------
//
void CDiagVibratePlugin::VibraStatusChanged( TVibrateStatus aStatus )
    {
    CDiagVibratePlugin::TState newState = EStateUnknown; 
    
    switch ( aStatus )
        {
        case EVibrateStarted:
            //The vibra can be started only from the idle state
            newState = ( iState == EStateIdle || iState == EStateRestart) ? EStateVibrating : EStateUnknown;
            break;
             
        case EVibrateEnded:
            //Vibra should not be stopped before it is started
            newState = ( iState == EStateVibrating ) ? EStateUserQuery : EStateUnknown;
            break;
            
        case EVibrateFailed:
            //check if already failed, cancelled or skipped
            newState = ( iState != EStateFailed && iState != EStateCancelled &&
                         iState != EStateSkipped ) ? EStateFailed : EStateUnknown;
            break;        
                
        case EVibrateBlocked:
			newState = ( iState == EStateVibrating ) ? EStateBlocked : EStateUnknown;
			break;
        default: //should never happen..
            __ASSERT_DEBUG( 0, User::Panic( KVibratePluginErrorUnknownVibraStateText,
                EPanicUnknownVibraState ) );
            return;
        }
        
    if ( newState != EStateUnknown) // Check if new state is available
        {
        SetState( newState );
        SetActive();
        TRequestStatus* status = &iStatus;
        User::RequestComplete(status, KErrNone);
        }
        
    }

// ----------------------------------------------------------------------------
// From MProgressDialogCallback
// Progress dialog has been dismissed
// ----------------------------------------------------------------------------
//
void CDiagVibratePlugin::DialogDismissedL( TInt aButtonId )
    {
    //Processing the user input only if the state is still vibrating
    if ( iState == EStateVibrating )
        {
        iEngine->StopVibra();

        CDiagVibratePlugin::TState newState = EStateUnknown; 
        
        switch ( aButtonId )
            {
            case ECBACmdSkip:
                newState = EStateSkipped;
                break;

            case ECBACmdCancel:
                newState = EStateCancelled;
                break;
                
            default:
                break;
            }
        
        if ( newState != EStateUnknown )
            {
            SetState( newState );
            SetActive();
            TRequestStatus* status = &iStatus;
            User::RequestComplete(status, KErrNone);
            }
        }
    else if(aButtonId == EAknSoftkeyOk)
        {
        CDiagVibratePlugin::TState newState = EStateUserQuery;
        SetState( newState );
        SetActive();
        TRequestStatus* status = &iStatus;
        User::RequestComplete(status, KErrNone);
        }
     
    }

// ----------------------------------------------------------------------------
// CDiagVibratePlugin::CDiagVibratePlugin()
//
// Constructor
// ----------------------------------------------------------------------------
//
CDiagVibratePlugin::CDiagVibratePlugin( CDiagPluginConstructionParam* aParam )
    :   CDiagTestPluginBase( aParam ),
        iState( EStateCancelled )
    {
    }


// ---------------------------------------------------------------------------
// CDiagVibratePlugin::ConstructL()
//
// Symbian OS two-phased constructor
// ---------------------------------------------------------------------------
//
void CDiagVibratePlugin::ConstructL()
    {
    BaseConstructL ( KDiagVibratePluginResourceFileName );
    ReadVibrationTimeL();
    }


// ---------------------------------------------------------------------------
// From CDiagTestPluginBase
// CDiagVibratePlugin::DoRunTestL()
// ---------------------------------------------------------------------------
//
void CDiagVibratePlugin::DoRunTestL()
    {
    SetState( EStateIdle );

    //Let's confirm from the user that the test should be run
    FOREVER
        {
        CAknMessageQueryDialog* dialog = new ( ELeave ) CAknMessageQueryDialog();
        dialog->PrepareLC( R_VIBRATEPLUGIN_STARTQUERY );
        
        // If the test is run as a single test removing skip button
        if ( SinglePluginExecution() )
            {
            CEikButtonGroupContainer& cba = dialog->ButtonGroupContainer();
            cba.SetCommandSetL( R_VIBRATE_SOFTKEYS_OK__CANCEL );
            }

        TInt queryResult;
        
        if ( !RunWaitingDialogL( dialog, queryResult ) )
            {
            // Dialog is dismissed by deletion. Exit immediately without
            // accessing member variable, to prevent crash
            return;
            }

        //Let's check the user answer
        switch ( queryResult )
            {
            
            // ADO & Platformization Changes
            //case ECBACmdSkip:
            case ECBACmdCancel:
                CompleteTestL( CDiagResultsDatabaseItem::ESkipped );
                return;

			case ECBACmdSkip:
			
            //case ECBACmdCancel:
                {
                //if ( SinglePluginExecution() )
                  //  {
                   // CompleteTestL( CDiagResultsDatabaseItem::ECancelled );
                   // return;
                   // }
               // else 
                 //   {
                      // See if the user really wants to cancel tests.
                    TInt confirmResult = 0;
                
                    CAknDialog* dlg = ExecutionParam().Engine().
                    CreateCommonDialogLC( EDiagCommonDialogConfirmSkipAll, NULL );
                
                    if ( !RunWaitingDialogL( dlg, confirmResult ) )
                        {
                        return;
                        }
                    /*
                    if ( confirmResult != EAknSoftkeyNo )
                        {
                       CompleteTestL( CDiagResultsDatabaseItem::ECancelled ); 
                        return;
                        }
                    */
                    if (confirmResult)
                           {
                           //CompleteTestL( CDiagResultsDatabaseItem::ECancelled ); 
                           return;
                           }
                    
                 //   }
              
                // Break and display the dialog again.
                }
                break;
                // Changes ends
                
            case EAknSoftkeyOk:
                {
                //Let's check that the charger is not connected
                
                if ( IsChargerConnected() && !DisplayDisconnectChargerQueryL() )
                    {
                    return;
                    }
                //Let's start the test by activating active object
                SetActive();
                TRequestStatus* status = &iStatus;
                User::RequestComplete(status, KErrNone);
                return;
                }
            default:
                return;
            }
        }
    }


// ---------------------------------------------------------------------------
// From CActive
// CDiagVibratePlugin::RunL
// ---------------------------------------------------------------------------
//
void CDiagVibratePlugin::RunL()
    {

	// ravi - badwarning #546-D    
    TRequestStatus* status = &iStatus;
    
    switch (iState) 
        {
        case EStateRestart:
        	{
        	iEngine->StopVibra();
        	LOGSTRING( "In RunL, EStateRestart");
            ShowVibratingNoteL();
            iEngine = CDiagVibratePluginEngine::NewL( *this );    
            TRAPD(Err,iEngine->StartVibraL( iVibrationTime ));
            if (Err == KErrNone)
            LOGSTRING("Leave code for restart = Kerrnone");
            LOGSTRING2( "Leave Code for in Restart StartVibra in Hex( 0x%x )", Err );
            break;
        	}
        case EStateIdle:
            if(!SetVibrateSettingOnL())
            	break;
            ShowVibratingNoteL();
            iEngine = CDiagVibratePluginEngine::NewL( *this );    
            iEngine->StartVibraL( iVibrationTime );
            break;
            
        case EStateVibrating:
            //Vibration started -> no action needed
            break;
            
        case EStateUserQuery: 
            {
            delete iEngine;
            iEngine = NULL;
            
            if ( iProgressDialog )
                {
                iProgressDialog->ProcessFinishedL();
                }
 
            SetVibrateSettingToOriginalValueL();
            //displaying user query: did the phone vibrate

            CAknQueryDialog* dialog = new ( ELeave ) CAknQueryDialog( CAknQueryDialog::ENoTone );
            dialog->PrepareLC( R_VIBRATEPLUGIN_CONFQUERY_DLG );

            TInt queryResult;
            if ( RunWaitingDialogL( dialog, queryResult ) )
                {
                //Let's check the user answer    
                switch ( queryResult )
                    {
                    case EAknSoftkeyYes:
                        LOGSTRING("CDiagVibratePlugin-EAknSoftkeyYes")
                        CompleteTestL( CDiagResultsDatabaseItem::ESuccess );
                        break;
                        
                    case ECBACmdNo:
                        LOGSTRING("CDiagVibratePlugin-EAknSoftkeyNo")
                        CompleteTestL( CDiagResultsDatabaseItem::EFailed );
                        break;
                    
                    default:
                        LOGSTRING("CDiagVibratePlugin-Default")
                        break;
                    }
                }
            }
            break;
            
        case EStateCancelled:
            {
             if ( SinglePluginExecution() )
                {
                CompleteTestL( CDiagResultsDatabaseItem::ECancelled );
                return;
                }
             else 
                {                                                    
                TInt confirmResult = 0;
                CAknDialog* dlg = ExecutionParam().Engine().
                CreateCommonDialogLC( EDiagCommonDialogConfirmCancelAll, NULL );
                
                if ( !RunWaitingDialogL( dlg, confirmResult ) )
                    {
                    //the dialog got deleted, returning right away
                    return;
                    }                    
                    
                if ( confirmResult == EAknSoftkeyYes )
                    {
                    //test is cancelled
                    CompleteTestL( CDiagResultsDatabaseItem::ECancelled );
                    }
                else //EAknSoftKeyNo
                    {
                    //let's rerun the test
                    DoStopAndCleanupL();
                    DoRunTestL();
                    }
                }
            }
            break; 
        
        case EStateSkipped:
            CompleteTestL( CDiagResultsDatabaseItem::ESkipped );
            break;
       
        case EStateFailed:
            CompleteTestL( CDiagResultsDatabaseItem::EFailed );
            break;
        
        case EStateBlocked:
        	LOGSTRING( "In RunL, EStateBlocked");
        	if ( iProgressDialog )
        		{
        		iProgressDialog->ProcessFinishedL();       
        		}
        	if (!DisplayDisconnectChargerQueryL() )
               {
                return;
               }
                //Let's start the test by activating active object
            SetState( EStateRestart );
            SetActive();
            User::RequestComplete(status, KErrNone);

        	break;
            
        default:
            __ASSERT_DEBUG( 0, User::Panic( KVibratePluginErrorUnknownVibraStateText,
                EPanicUnknownVibraState ) );
            break;    
        }
    }    


// ---------------------------------------------------------------------------
// From CActive
// CDiagVibratePlugin::DoCancel
// ---------------------------------------------------------------------------
//
void CDiagVibratePlugin::DoCancel()
    {
    }    


// ---------------------------------------------------------------------------
// Cleanup the created resources
// CDiagVibratePlugin::DoStopAndCleanupL
// ---------------------------------------------------------------------------
//
void CDiagVibratePlugin::DoStopAndCleanupL()
    {
    delete iEngine;    
    iEngine = NULL;
    if ( iProgressDialog )
        {
        iProgressDialog->ProcessFinishedL();       
        }
    SetVibrateSettingToOriginalValueL();
    iVibrateSettingUpdated = EFalse;
    if ( iState != EStateCancelled )
        {
        SetState( EStateCancelled );
        }
    }

    
// ---------------------------------------------------------------------------
// Makes sure vibrate is enabled in the profile
// CDiagVibratePlugin::SetVibrateSettingOnL
// ---------------------------------------------------------------------------
//
TInt CDiagVibratePlugin::SetVibrateSettingOnL()
    {
    MProEngEngine* profileEng = ProEngFactory::NewEngineL();
    CleanupDeletePushL(profileEng);

    MProEngProfile* profile = profileEng->ActiveProfileL();
    MProEngToneSettings& profileToneSettings = profile->ToneSettings();
    iOrigVibrateSetting = profileToneSettings.VibratingAlert();
   
   //set the vibration setting on if it is not on already
    if (!iOrigVibrateSetting)
        {
        if(!ShowActivateConfNoteL())
        {
        		profile->Release();    
    			CleanupStack::PopAndDestroy(profileEng);
    			return EFalse;
    	}
        profileToneSettings.SetVibratingAlert(ETrue);
        profile->CommitChangeL();
        iVibrateSettingUpdated = ETrue;
        }
    
    profile->Release();    
    CleanupStack::PopAndDestroy(profileEng);
    return ETrue;
    }
    
    
TInt CDiagVibratePlugin::ShowActivateConfNoteL()
{
	CAknQueryDialog * dialog = new (ELeave) CAknQueryDialog();
    dialog->PrepareLC( R_VIBRATEPLUGIN_VIBRATE_ACTIVATE_DLG );
    
    
    TInt queryResult;
        
    if ( !RunWaitingDialogL( dialog, queryResult ) )
         {
         // Dialog is dismissed by deletion. Exit immediately without
         // accessing member variable, to prevent crash
         return EFalse;
         }

        //Let's check the user answer
        switch ( queryResult )
            {
            
            case ECBACmdNo:
                CompleteTestL( CDiagResultsDatabaseItem::ESkipped );
                return EFalse;

            case EAknSoftkeyYes:
            	return ETrue;

            default:
                return EFalse;
            }	
	
}

    
// ---------------------------------------------------------------------------
// Sets the vibration setting to its original value
// CDiagVibratePlugin::SetVibrateSettingToOriginalValueL
// ---------------------------------------------------------------------------
//
void CDiagVibratePlugin::SetVibrateSettingToOriginalValueL()
    {
    if ( !iVibrateSettingUpdated )
        {
        return;
        }
        
    MProEngEngine* profileEng = ProEngFactory::NewEngineL();
    MProEngProfile* profile = profileEng->ActiveProfileL();
    MProEngToneSettings& profileToneSettings = profile->ToneSettings();
    
    //Check if vibrate setting need to be turned off again
    if ( !iOrigVibrateSetting )
        {
        profileToneSettings.SetVibratingAlert( EFalse );
        profile->CommitChangeL();
        }
        
    profile->Release();
    profileEng->Release();                

    iVibrateSettingUpdated = EFalse;
    }    


// ---------------------------------------------------------------------------
// Checks if the charger is connected if yes the user is asked to removed it.
// CDiagVibratePlugin::IsChargerConnected
// ---------------------------------------------------------------------------
//
TBool CDiagVibratePlugin::IsChargerConnected() const
    {
    TInt charger;
    TBool result = EFalse;
    
    if( RProperty::Get( KPSUidHWRMPowerState, KHWRMChargingStatus, charger ) == KErrNone )
        {
        switch( charger )
            {
            case EChargingStatusCharging:
            case EChargingStatusNotCharging:
            case EChargingStatusChargingComplete:
            case EChargingStatusAlmostComplete:
            case EChargingStatusChargingContinued:
                result = ETrue;
                break;
                
            case EChargingStatusNotConnected:
            default:
                break;
            }
        }
        
    return( result );
    }


// ---------------------------------------------------------------------------
// Sets the internal state
// CDiagVibratePlugin::SetState
// ---------------------------------------------------------------------------
//
void CDiagVibratePlugin::SetState( CDiagVibratePlugin::TState aState )
    {
    __ASSERT_DEBUG( iState != aState, User::Panic( KVibratePluginErrorStateRepeatedText,
                EPanicStateRepeated ) );

    iState = aState;
    }

// ---------------------------------------------------------------------------
// Displays disconnect charger query
// CDiagVibratePlugin::DisplayDisconnectChargerQuery
// ---------------------------------------------------------------------------
//
TBool CDiagVibratePlugin::DisplayDisconnectChargerQueryL()
    {
    FOREVER
        {
        CAknQueryDialog* dialog = new ( ELeave ) CAknQueryDialog( CAknQueryDialog::ENoTone );
        dialog->PrepareLC( R_VIBRATEPLUGIN_DISCONNECT_CHARGER_DLG );


        TInt queryResult;
        
        if ( !RunWaitingDialogL( dialog, queryResult ) )
            {
            // Dialog is dismissed by deletion. Exit immediately without
            // accessing member variable, to prevent crash
            return EFalse;
            }

        //Let's check the user answer
        switch ( queryResult )
            {
            
            case ECBACmdCancel:
                {
                if ( SinglePluginExecution() )
                    {
                    CompleteTestL( CDiagResultsDatabaseItem::ESkipped );
                    return EFalse;
                    }
                else
                    {
                    // See if the user really wants to cancel tests.
                    TInt confirmResult = 0;
                    
                    CAknDialog* dlg = ExecutionParam().Engine().
                        CreateCommonDialogLC( EDiagCommonDialogConfirmCancelAll, NULL );
                    
                    if ( !RunWaitingDialogL( dlg, confirmResult ) )
                        {
                        return EFalse;
                        }
                        
                    if ( confirmResult)
                        {
                        return EFalse;
                        }
                    else
                        {
                        continue;
                        }
                    }

                // Break and display the dialog again.
                }
                
            case EAknSoftkeyOk:
                if ( !IsChargerConnected() )
                    {
                    return ETrue;   
                    }
                break;
            default:
                return EFalse;
            }
        }
    }


// ---------------------------------------------------------------------------
// Displays vibrating progress dialog
// ---------------------------------------------------------------------------
//
void CDiagVibratePlugin::ShowVibratingNoteL()
    {
    if ( iProgressDialog )
        {
        delete iProgressDialog;
        iProgressDialog = NULL;
        }
    
    //Calculate progress bar update interval in hundredths of seconds
    TInt interval = KHundredthsInSecond * iVibrationTime *
                    ( ( TReal ) KProgressDialogIncrementSize / 
                    KProgressDialogFinalValue );
                    
    iProgressDialog = new ( ELeave ) CAknProgressDialog( 
                        KProgressDialogFinalValue, 
                        KProgressDialogIncrementSize,
                        interval,
                        reinterpret_cast <CEikDialog**> ( &iProgressDialog ) );

    iProgressDialog->SetCallback( this );
    iProgressDialog->PrepareLC( R_VIBRATEPLUGIN_PROGRESS_NOTE_VIBRATING );
    
    // If the test is run as a single test removing skip button
    if ( SinglePluginExecution() )
        {
        CEikButtonGroupContainer& cba = iProgressDialog->ButtonGroupContainer();
        cba.SetCommandSetL( R_VIBRATE_SOFTKEYS_STOP__CANCEL );
        }

    iProgressDialog->RunLD();
    }


// ---------------------------------------------------------------------------
// Reads vibration time from central repository
// ---------------------------------------------------------------------------
//
void CDiagVibratePlugin::ReadVibrationTimeL()
    {
    LOGSTRING( "CDiagVibratePlugin::ReadVibrationTimeL begin" );
                
    CRepository* repository = CRepository::NewLC( KCRUidDevDiagVibraTestPlugin );  
         
    User::LeaveIfError( repository->Get( KDevDiagVibraTestVibrateTime, iVibrationTime ) );
    
    CleanupStack::PopAndDestroy( repository );
    
    LOGSTRING( "CDiagVibratePlugin::ReadVibrationTimeL end" );
    }
    

// End of File
