/*
* Copyright (c) 2005-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:  Handles shutdown situation.
*
*/

#include "AknCapServerShutdown.h"
#include <apgwgnam.h>
#include <coemain.h>
#include <apgtask.h>
#include <coedef.h>
#include <eiksvdef.h>

#include <gfxtranseffect/gfxtranseffect.h>
#include "akntranseffect.h" // for Transition effect enumerations

// Delay after sending rogue apps a kill signal before completing the shutdown.
const TInt KEikServShutdownAppKillDelay = 1000000;  // 1 second 
const TUid KCapServerUid = { 0x10207218 };
const TUid KFepSwitchWGId = {0xfabbabba}; // hoping that value is to stay...

_LIT( KViewServerThreadName, "ViewServerThread" );
_LIT( KUikonWatchersThreadName, "UikonWatchers" );
_LIT( KAknCapServerThreadName, "akncapserver" ); 
_LIT( KEikAppUiServerThreadName, "EikAppUiServerThread" );	
_LIT( KEikAppClock2ThreadName, "adtupdater" );	
_LIT( KEikAppBsengineThreadName, "bsengine" );	

// shut critical thread may cause system to reset during shut up process.
TBool IsSystemCriticalThread( const RThread& aThread )
    {
    if ( ( User::Critical( aThread ) == User::ESystemCritical ) ||
         ( aThread.Name() == KViewServerThreadName ) ||
         ( aThread.Name() == KAknCapServerThreadName ) ||
         ( aThread.Name() == KUikonWatchersThreadName ) ||
         ( aThread.Name() == KEikAppClock2ThreadName ) ||
         ( aThread.Name() == KEikAppBsengineThreadName ) ||
		 ( aThread.Name() == KEikAppUiServerThreadName ) )
        {
        return ETrue;
        }

    return EFalse;
    }

CAknCapServerShutdown::CAknCapServerShutdown()
: iWs(CCoeEnv::Static()->WsSession())
    {
    }
    
CAknCapServerShutdown::~CAknCapServerShutdown()
    {
    if (iAppExitNotifiers)
        {
        iAppExitNotifiers->ResetAndDestroy();
        }
    delete iAppExitNotifiers;
    delete iShutdownTimeout;
    }
    
void CAknCapServerShutdown::ConstructL()
    {
#ifdef _DEBUG
    RDebug::Print(_L("Creating app exit notifiers"));
#endif
    iAppExitNotifiers = new (ELeave) CArrayPtrFlat<CAppExitNotifier>(4);
    }

void CAknCapServerShutdown::ShutdownAppsL(
    const TUid aRequesterUID, 
    const RMessage2& aShutdownMessage, 
    const TInt aTimeoutInMicroseconds)
    {
    GfxTransEffect::BeginFullScreen(
        AknTransEffect::EApplicationExit,
        TRect(0,0,0,0),
        AknTransEffect::EParameterType,
        AknTransEffect::GfxTransParam(KCapServerUid, AknTransEffect::TParameter::EAvkonCheck));
      
    // Exit all apps currently running, with the exception of the one whose UID is passed in.
    iShutdownMessage = aShutdownMessage;

    CArrayFixFlat<TInt>* wgIds = new(ELeave) CArrayFixFlat<TInt>(4);
    CleanupStack::PushL(wgIds);
    User::LeaveIfError(iWs.WindowGroupList(0, wgIds));
    TInt lastEntry = wgIds->Count() - 1;

    iTotalAppExitNotifiers = 0;
    iAppExitNotifiers->ResetAndDestroy();

    for ( TInt ii = lastEntry; ii >= 0; ii-- )
      {
      CApaWindowGroupName* doomed = CApaWindowGroupName::NewLC(iWs, wgIds->At(ii));
      RThread thd;
      TThreadId threadId;

#ifdef _DEBUG
      TBool isSystem = doomed->IsSystem();
      TBool isHidden = doomed->Hidden();
#endif // _DEBUG
      // This UID comes from the app, not the mmp!
      TUid uid = doomed->AppUid(); 
      iWs.GetWindowGroupClientThreadId(wgIds->At(ii), threadId);
      TInt error = thd.Open(threadId);  
	  CleanupClosePushL( thd );

      // Is this app OK to kill? We don't kill the this app, EikSrv backdrop or the app that 
      // instigated the shutdown.
      if ((uid != aRequesterUID) && ( uid != KCapServerUid ) && ( uid != KFepSwitchWGId ) && 
             ( doomed->Caption() != EIKON_SERVER_BACKDROP_WINDOW_GROUP_NAME ) &&
			  !IsSystemCriticalThread( thd ) && (!doomed->IsSystem()) && (uid.iUid != 0))
         {
         TApaTask* harbingerOfDoom = new (ELeave) TApaTask(iWs);
         CleanupDeletePushL(harbingerOfDoom);
         harbingerOfDoom->SetWgId(wgIds->At(ii));
                
#ifdef _DEBUG   // Silliness to prevent "using lvalue as rvalue" warnings.
         TPtrC caption(doomed->Caption()); // doomed->Caption());
         TPtrC docname(doomed->DocName()); // doomed->DocName());
         TPtrC wgname(doomed->WindowGroupName());  // doomed->WindowGroupName());
             
         _LIT(KDebugShutdownMsg1, "SHUTDOWN: Exiting App (ThreadId %d, WgId %d, ");
         RDebug::Print(KDebugShutdownMsg1, TUint(harbingerOfDoom->ThreadId()), wgIds->At(ii));
         _LIT(KDebugShutdownMsg2, "UID 0x%X); Caption: %S, ");
         RDebug::Print(KDebugShutdownMsg2, uid.iUid, &caption);
             
         _LIT(KDebugShutdownMsg3, "Docname: %S, system %d, ");
         RDebug::Print(KDebugShutdownMsg3, &docname, isSystem);
             
         _LIT(KDebugShutdownMsg4, "hidden %d, WGName : %S");
         RDebug::Print(KDebugShutdownMsg4, isHidden, &wgname);
         TPtrC threadName(thd.Name());
         RDebug::Print(_L("thread:%S)"), &threadName );
#endif // _DEBUG
         CAppExitNotifier* exiter = CAppExitNotifier::NewL(harbingerOfDoom, this);
         CleanupStack::Pop(); // harbingerOfDoom
         CleanupStack::PushL(exiter);
         iAppExitNotifiers->AppendL(exiter);
         iTotalAppExitNotifiers++;
         CleanupStack::Pop(); //exiter
         exiter->ExitTask();
         } 
#ifdef _DEBUG           
      else 
         {
         TPtrC caption = doomed->Caption();
         TPtrC threadName(thd.Name());
         RDebug::Print(_L("SHUTDOWN: privileged App %S(thread:%S) is not being closed"), &caption, &threadName );
         }
#endif

      CleanupStack::PopAndDestroy( &thd ); //thd
      CleanupStack::PopAndDestroy();  //doomed
      }

    // If no apps were running, complete straight away.
    if (iTotalAppExitNotifiers == 0)
        {
        iShutdownMessage.Complete(KErrNone);
        }
    else
        {
        // Start the timeout timer.
        iShutdownTimeout = CPeriodic::NewL(CActive::EPriorityHigh); 
        
        iShutdownTimeout->Start(
            aTimeoutInMicroseconds, 
            aTimeoutInMicroseconds, 
            TCallBack(ShutdownTimeoutL, this));
            
        iShutdownState=EShutdownWaitingForApps;
        }

    CleanupStack::PopAndDestroy(); // wgIds
    }
    
void CAknCapServerShutdown::CancelShutdownAppsL()
    {
    if ( !iShutdownMessage.IsNull() )
        {
        iShutdownMessage.Complete(KErrCancel);
        }
    }

// Static callback from Shutdown timeout.
TInt CAknCapServerShutdown::ShutdownTimeoutL(TAny* aPtr)
    {
    return (STATIC_CAST(CAknCapServerShutdown*,aPtr))->DoShutdownTimeoutL();
    }

TInt CAknCapServerShutdown::DoShutdownTimeoutL()
    {
    if (iShutdownState == EShutdownWaitingForApps)
        { //The timer has completed because not all apps have closed cleanly in the allotted time.
#ifdef _DEBUG
        RDebug::Print(_L("SHUTDOWN: Timeout! Killing remaining apps"));
#endif
        delete iShutdownTimeout;
        iShutdownTimeout = NULL;
        
        // Kill any remaining apps forcibly.
        for (TInt ii = 0; ii < iAppExitNotifiers->Count(); ii++)
            {
            if (!iAppExitNotifiers->At(ii)->IsDead())
                {
                iAppExitNotifiers->At(ii)->KillTask();
                }
            }
        
        // Now the remaining apps have been killed, there is no need to wait any longer.
        // We can allow the machine to turn off after a short delay.
        iShutdownTimeout = CPeriodic::NewL(CActive::EPriorityHigh); 
        
        iShutdownTimeout->Start(
            KEikServShutdownAppKillDelay, 
            KEikServShutdownAppKillDelay,
            TCallBack(ShutdownTimeoutL, this));
            
        iShutdownState = EShutdownKillingRogueApps;
        }
    else
        { 
        // The timer has completed because not all apps have responded to a kill request in the 
        // allotted time (this is bad).
#ifdef _DEBUG
        RDebug::Print(_L("SHUTDOWN: Error! At least one app failed to respond to kill request. Shutting down..."));
#endif
        ProceedWithShutdown();
        }

    return EFalse;
    }

// Callback from CAppExitNotifier.
void CAknCapServerShutdown::AppExitNotifierL(
    const CAppExitNotifier* aNotifier, 
    CAppExitNotifier::TAppExitMethod aHowClosed)
    {
    if (aHowClosed == CAppExitNotifier::EAppExitNormal)
        {
#ifdef _DEBUG
        RDebug::Print(_L("SHUTDOWN: App with ThreadId %d has exited"), TUint(aNotifier->ThreadId()));
#else
        aNotifier->ThreadId(); // just for fixing warning
#endif
        }
    else if (aHowClosed == CAppExitNotifier::EAppExitForced)
        {
#ifdef _DEBUG
        RDebug::Print(_L("SHUTDOWN: App with ThreadId %d was killed"), TUint(aNotifier->ThreadId()));
#else
        aNotifier->ThreadId(); // just for fixing warning
#endif
        }
    iTotalAppExitNotifiers--;
    
    // If all the apps have exited then complete the request.
    if (iTotalAppExitNotifiers == 0)
        {
        ProceedWithShutdown();
        }
    }
    
// Cleanup of objects used in shutdown, and signal to client that apps are all closed.
void CAknCapServerShutdown::ProceedWithShutdown()
    {
    // Complete the client message.
    iShutdownMessage.Complete(KErrNone);
    // Delete all the notifiers
    iAppExitNotifiers->ResetAndDestroy();
    // and stop the timeout timer.
    delete iShutdownTimeout;
    iShutdownTimeout=NULL;
    }

//
// class CAppExitNotifier
// 
CAknCapServerShutdown::CAppExitNotifier* CAknCapServerShutdown::CAppExitNotifier::NewL(
    TApaTask* aTask, 
    CAknCapServerShutdown* aObserver)
    {
    CAppExitNotifier* self = new (ELeave) CAppExitNotifier(aTask,aObserver);
    CleanupStack::PushL(self);
    self->ConstructL();
    CleanupStack::Pop(); //self
    return self;
    }

CAknCapServerShutdown::CAppExitNotifier::~CAppExitNotifier()
    {
    Cancel();
    delete iTask;
    }

void CAknCapServerShutdown::CAppExitNotifier::ExitTask()
    {
    iExitMethod = EAppExitNormal;
    iTask->EndTask();
    }

void CAknCapServerShutdown::CAppExitNotifier::KillTask()
    {
    iExitMethod = EAppExitForced;
    iTask->KillTask();
    }

TBool CAknCapServerShutdown::CAppExitNotifier::IsDead()
    {
    return iIsDead;
    }

const TApaTask& CAknCapServerShutdown::CAppExitNotifier::Task() const
    {
    return *iTask;
    }

const TThreadId CAknCapServerShutdown::CAppExitNotifier::ThreadId() const
    {
    return iThreadId;
    }

void CAknCapServerShutdown::CAppExitNotifier::ConstructL()
    {
    iThreadId = iTask->ThreadId();
    User::LeaveIfError(iThread.Open(iThreadId));
    iThread.Logon(iStatus);
    SetActive();
    }

void CAknCapServerShutdown::CAppExitNotifier::RunL()
    {
    iIsDead = ETrue;
    iObserver->AppExitNotifierL(this,iExitMethod);
    }

void CAknCapServerShutdown::CAppExitNotifier::DoCancel()
    {
    iThread.LogonCancel(iStatus);
    }

CAknCapServerShutdown::CAppExitNotifier::CAppExitNotifier(TApaTask* aTask, 
    CAknCapServerShutdown* aObserver) 
: CActive(EPriorityStandard),
    iObserver(aObserver),
    iTask(aTask)
    {
    CActiveScheduler::Add(this);
    }

// End of file
