diff -r 000000000000 -r 2e3d3ce01487 commonappservices/alarmserver/Server/Source/ASSrvSoundController.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/commonappservices/alarmserver/Server/Source/ASSrvSoundController.cpp Tue Feb 02 10:12:00 2010 +0200 @@ -0,0 +1,745 @@ +// Copyright (c) 1999-2009 Nokia Corporation and/or its subsidiary(-ies). +// All rights reserved. +// This component and the accompanying materials are made available +// under the terms of "Eclipse Public License v1.0" +// which accompanies this distribution, and is available +// at the URL "http://www.eclipse.org/legal/epl-v10.html". +// +// Initial Contributors: +// Nokia Corporation - initial contribution. +// +// Contributors: +// +// Description: +// + +#include "ASSrvSoundController.h" + +// System includes + +// User includes +#include "ASSrvAlarm.h" +#include "ASSrvAlarmQueue.h" +#include "ASSrvStaticUtils.h" +#include "ASSrvSoundSettings.h" +#include "ASSrvServerWideData.h" +#include "ASSrvAnyEventManager.h" +#include "ASSrvSoundControllerObserver.h" +#include "ASSrvEnvironmentChangeManager.h" + +#include "ASSrvIteratorByState.h" +#include "ASSrvNotificationCoordinator.h" + + +// +// ----> CASSrvSoundController (source) +// + + /** + * Indicates that the Sound Controller is disabled (ie. no sounds + * intervals configured in the rsc file) + */ +TBool CASSrvSoundController::IsSoundControllerDisabled() const + { + return 0==ServerData().SoundSettings().SoundIntervalCount(); + } + +//************************************************************************************* +CASSrvSoundController::CASSrvSoundController(CASSrvServerWideData& aServerWideData, MASSrvSoundControllerObserver& aObserver) +: CTimer(CActive::EPriorityUserInput), iServerWideData(aServerWideData), iObserver(aObserver) + { + iPreviousSoundEvent = MASSrvSoundControllerObserver::ESoundControllerEventPlayNothing; + CActiveScheduler::Add(this); + } + + +//************************************************************************************* +CASSrvSoundController::~CASSrvSoundController() + { + Cancel(); + + // It's ok to Cancel these even if we didn't register for the them: + ServerData().EnvironmentChangeManager().RequestEnvironmentChangesCancel(*this); + ServerData().SoundSettings().NotifySoundSettingsChangeCancel(*this); + ServerData().Queue().RequestAlarmObservationEventsCancel(*this); + ServerData().Queue().NotificationPoolChangeCancel(*this); + } + + +//************************************************************************************* +void CASSrvSoundController::ConstructL() + { + CTimer::ConstructL(); + + if( !IsSoundControllerDisabled() ) + { + ServerData().Queue().NotificationPoolChangeL(*this); + ServerData().Queue().RequestAlarmObservationEventsL(*this); + ServerData().SoundSettings().NotifySoundSettingsChangeL(*this); + ServerData().EnvironmentChangeManager().RequestEnvironmentChangesL(*this); + iPreviousUTCOffset = User::UTCOffset(); + iSoundControllerFlags.Set(ESoundControllerFlagsIsFixed); + } + } + + +//************************************************************************************* +CASSrvSoundController* CASSrvSoundController::NewL(CASSrvServerWideData& aServerWideData, MASSrvSoundControllerObserver& aObserver) + { + CASSrvSoundController* self = new(ELeave) CASSrvSoundController(aServerWideData, aObserver); + CleanupStack::PushL(self); + self->ConstructL(); + CleanupStack::Pop(self); + return self; + } + + +// +// +// + + +//************************************************************************************* +/** + * @see MASSrvAlarmObserver + * + * When an alarm expires, we should start timing the alarm sound intervals. Note that + * no sound is played directly by the alarm server - it's all handled by proxy in the + * EikSrv thread (Alarm Alert Server). + * + * We only ever receive one notification that an alarm has expired (unless its snoozed + * and then the snooze expires). Therefore, the sound controller has to perform cycle + * management for the alerting (notifying) head alarm in the queue). + */ +void CASSrvSoundController::MASSrvAlarmObsHandleEvent(TObserverEvent aEvent, const TASSrvAlarm& aAlarm, TInt aEventSpecificData) + { + // Get a modifiable version of the alarm + TASSrvAlarm* alarm = ServerData().Queue().QueueAlarmById(aAlarm.Id()); + + if (aEvent == EAlarmObserverStateChanged) + { + // Notifying / snoozed / notified / etc + const TAlarmState oldState = static_cast(aEventSpecificData); + if (oldState == EAlarmStateNotifying && IdOfNotifyingAlarm() == aAlarm.Id()) + { + // If the alarm was notifying, but now it's back in the queue + // then we need to cancel any sound playing in preparation for + // the new notifying alarm + if (!InQuietPeriod()) + { + Cancel(); + } + + if (SoundIsPausedForAlarm()) + { + iSoundControllerFlags.Clear(ESoundControllerFlagsPausing); + } + + // Reset this now + IdOfNotifyingAlarm() = KNullAlarmId; + } + // + switch(aAlarm.State()) + { + case EAlarmStateNotifying: + { + // The only thing we really care about is that the new notifying alarm is not null. + __ASSERT_DEBUG(aAlarm.Id() != KNullAlarmId, + ASSrvStaticUtils::Panic(ASSrvStaticUtils::EASSrvPanicQueueAlarmIdNull)); + + // Ensure Alarm Disabled status change didn't slip in ahead in server queue + if (alarm->Status() == EAlarmStatusEnabled) + { + if (InQuietPeriod()) + { + // delay any sound playing until the quiet period ends + alarm->SetSoundState(TASSrvAlarm::ESoundStatePlayingNothing); + IdOfNotifyingAlarm() = aAlarm.Id(); + } + else + { + Cancel(); + + // Work out when we should start playing the sound + IdOfNotifyingAlarm() = aAlarm.Id(); + WorkOutAndScheduleForNextSoundCycle(); + } + } + + break; + } + + case EAlarmStateWaitingToNotify: + case EAlarmStateSnoozed: + case EAlarmStateNotified: + // An alarm is no longer notifying, this is handle above already + break; + + // We're not interested in these kind of events + case EAlarmStateQueued: + case EAlarmStateInPreparation: + return; + } + } + else if (aEvent == EAlarmObserverStatusChanged) + { + // Enabled / disabled + if (aAlarm.Id() == IdOfNotifyingAlarm() && alarm->Status() == EAlarmStatusDisabled) + { + // Cancel sound & timer + if (!InQuietPeriod()) + { + Cancel(); + } + + // Reset this now + IdOfNotifyingAlarm() = KNullAlarmId; + } + } + + // If there is no alarm playing (b/c the previous playing alarm is snoozed, + // notified, disabled etc), but there are multiple notifying alarms, + // restart sound playing sequence for another notifying alarm + if (IdOfNotifyingAlarm() == KNullAlarmId && !InQuietPeriod()) + { + PlayPreviousNotifyingAlarm(); + } + } + + +// +// +// + + +//************************************************************************************* +/** + * @see MASSrvSoundSettingsObserver + */ +void CASSrvSoundController::MASSoundSettingsHandleChangeEvent(TSoundSettingsEvent aEvent) + { + CASSrvSoundSettings& soundSettings = ServerData().SoundSettings(); + // + switch(aEvent) + { + case ESoundSettingsEventGlobalSoundState: + if (soundSettings.GlobalSoundState() == EAlarmGlobalSoundStateOff) + { + // Stop playing anything right now + CancelAllSounds(); + } + else + { + // Resume sound timing from now + iTimeToReturnToNormalSoundTimingBehaviour = ASSrvStaticUtils::UtcTimeNow(); + + StartSoundTimingForAnyWaitingAlarmAfterPausingOrQuietPeriod(); + } + + break; + + case ESoundSettingsEventSoundIntervals: + // Resume sound timing from now + CancelAllSounds(); + iTimeToReturnToNormalSoundTimingBehaviour = ASSrvStaticUtils::UtcTimeNow(); + + StartSoundTimingForAnyWaitingAlarmAfterPausingOrQuietPeriod(); + break; + + case ESoundSettingsClearPauseFlag: + iSoundControllerFlags.Clear(ESoundControllerFlagsPausing); + break; + + default: + ASSrvStaticUtils::Panic(ASSrvStaticUtils::EASSrvPanicInvalidTSoundSettingsEvent); + break; + } + } + + +// +// +// + + +//************************************************************************************* +/** + * @see MASSrvAlarmQueueObserver + */ +void CASSrvSoundController::MAlarmQueueObserverHandleEvent(TASSrvAlarmQueueEvent aEvent, TAlarmId aAlarmId) + { + /* If a notifying alarm is deleted stop playing alarm sound. + * Looking for either: + * 1. The currently Notifying alarm is deleted. + * 2. Alarm Queue Internalize starts whilst any alarm is notifying + */ + if ((aEvent == EASSrvAlarmQueueEventAlarmDeleted) && (aAlarmId == IdOfNotifyingAlarm())) + { + // Cancel the sound and timer, and clear the id of the alarm (we're + // no longer notifying about anything). + // Cancel sound & timer + if (!InQuietPeriod()) + { + Cancel(); + } + + IdOfNotifyingAlarm() = KNullAlarmId; + + // If a notifying alarm is deleted, look for the previous notifying alarm. + // The following function is called even if we are in quiet period + // because we want the appropriate alarm to be played when quite period end. + PlayPreviousNotifyingAlarm(); + } + else if ((aEvent == EASSrvAlarmQueueEventAlarmStartInternalize) && (KNullAlarmId != IdOfNotifyingAlarm())) + { + // Cancel the sound and timer, and clear the id of the alarm (we're + // no longer notifying about anything). + // Cancel sound & timer + if (!InQuietPeriod()) + { + Cancel(); + } + + IdOfNotifyingAlarm() = KNullAlarmId; + } + } + +//************************************************************************************* +/** + * @see MASSrvEnvironmentChangeObserver + */ + +void CASSrvSoundController::MEnvChangeHandleEvent(TInt aChanges, TUint /*aWorkdays*/, TBool /*aWorkdaysChanged*/) + { + if (aChanges & EChangesSystemTime || (aChanges & EChangesLocale && (iPreviousUTCOffset != User::UTCOffset())) ) + { + // UTC offset might have changed we need to redo the CTimer::AtUTC with a new UTC offset computed + + if (!iSoundControllerFlags.IsSet(ESoundControllerFlagsIsFixed)) // if floating period + { + iTimeToReturnToNormalSoundTimingBehaviour -= TTimeIntervalSeconds(User::UTCOffset().Int() - iPreviousUTCOffset.Int()); + } + else //if fixed period + { + //cancel silent period because we can't recalculate it + iTimeToReturnToNormalSoundTimingBehaviour = ASSrvStaticUtils::UtcTimeNow(); + } + + if (InQuietPeriod() || SoundIsPausedForAlarm()) + { + Cancel(); + + if(iTimeToReturnToNormalSoundTimingBehaviour <= ASSrvStaticUtils::UtcTimeNow()) + { + ReactToSoundTimerExpiry(); // Silent period expired + } + else + { + AtUTC(iTimeToReturnToNormalSoundTimingBehaviour); + } + } + else if (IdOfNotifyingAlarm() != KNullAlarmId) + { + // resume sound timing from now because of changed system time + StartSoundTimingForAnyWaitingAlarmAfterPausingOrQuietPeriod(); + } + + // And update the UTC offset + iPreviousUTCOffset = User::UTCOffset(); + } + } + + +// +// +// + + +//************************************************************************************* +/** + * Stops any sound from playing and resets the sound timer to an idle state. + */ +void CASSrvSoundController::CancelAllSounds() + { + // Tell observer to stop + if( !IsSoundControllerDisabled() ) + { + NotifySoundEvent(MASSrvSoundControllerObserver::ESoundControllerEventPlayNothing, IdOfNotifyingAlarm()); + } + } + + +//************************************************************************************* +/** + * Stop playing any sounds until the specified time. + */ +void CASSrvSoundController::MakeAllSoundsQuietUntil(const TTime& aTime) + { + if( IsSoundControllerDisabled() ) + { + return; + } + + Cancel(); + + // Update the flags + iSoundControllerFlags.Set(ESoundControllerFlagsInQuietPeriod); + + // set global sound state + ServerData().SoundSettings().SetGlobalSoundState(EAlarmGlobalSoundStateOff); + + // Wake up when sounds are to start again + iTimeToReturnToNormalSoundTimingBehaviour = aTime; + AtUTC(iTimeToReturnToNormalSoundTimingBehaviour); + + // Notify change + ServerData().AnyEventManager().MASAnyEventManagerObserverNotifyChanges(EAlarmChangeEventSoundSilence, KNullAlarmId); + } + +//************************************************************************************* +/** + * Stop playing the sound (for just this alarm) until the specified time. + */ +void CASSrvSoundController::CurrentAlarmSoundPausedUntil(const TTime& aTime) + { + if( IsSoundControllerDisabled() ) + { + return; + } + + Cancel(); + + // Save the time we're supposed to wake up at and reset + // the alarm's sound state so that it can start over + // again when we're due to run. + TASSrvAlarm* alarm = ServerData().Queue().QueueAlarmById(IdOfNotifyingAlarm()); + + // We have a new baseline for all sound timing calculations. The next set of + // cycle calcs must be based upon the time at which the quiet period ends + // rather than the original due time. + alarm->ReinitializeSoundState(aTime); + + // Update the flags + iSoundControllerFlags.Set(ESoundControllerFlagsPausing); + + // Wake up when sounds are to start again + iTimeToReturnToNormalSoundTimingBehaviour = aTime; + + AtUTC(aTime); + + // Notify change + ServerData().AnyEventManager().MASAnyEventManagerObserverNotifyChanges(EAlarmChangeEventSoundSilence, KNullAlarmId); + } + + +//************************************************************************************* +/** + * Returns a boolean indicating whether or not the alarm server is currently + * in a quiet period. + */ +TBool CASSrvSoundController::InQuietPeriod() const + { + if( IsSoundControllerDisabled() ) + { + return ETrue; + } + return Flags().IsSet(ESoundControllerFlagsInQuietPeriod); + } + + +//************************************************************************************* +/** + * Return a boolean indicating whether sound is currently + * paused within the alarm server. + */ +TBool CASSrvSoundController::SoundIsPausedForAlarm() const + { + if( IsSoundControllerDisabled() ) + { + return EFalse; + } + return Flags().IsSet(ESoundControllerFlagsPausing); + } + + +//************************************************************************************* +/** + * Cancel a previously enabled silent period + */ +void CASSrvSoundController::CancelSilence() + { + if( IsSoundControllerDisabled() ) + { + return; + } + + if (iSoundControllerFlags.IsSet(ESoundControllerFlagsPausing) || iSoundControllerFlags.IsSet(ESoundControllerFlagsInQuietPeriod)) + { + Cancel(); + + // Update the time to use as a baseline for the next bout of sounds + iTimeToReturnToNormalSoundTimingBehaviour = ASSrvStaticUtils::UtcTimeNow(); + + // Update the flags + iSoundControllerFlags.Clear(ESoundControllerFlagsPausing); + iSoundControllerFlags.Clear(ESoundControllerFlagsInQuietPeriod); + + // set global sound state + ServerData().SoundSettings().SetGlobalSoundState(EAlarmGlobalSoundStateOn); + + // Notify change + ServerData().AnyEventManager().MASAnyEventManagerObserverNotifyChanges(EAlarmChangeEventSoundSilence, KNullAlarmId); + } + } + + +// +// +// + + +//************************************************************************************* +/** + * @see CActive + */ +void CASSrvSoundController::RunL() + { + const TInt statusValue = iStatus.Int(); + + // When the user changes the system time, then the timer's request status + // is completed with KErrAbort. If we don't gracefully trap this here, then + // lots of strange thing start to happen (dialogs don't get dismissed in EikSrv etc). + if (statusValue == KErrAbort || statusValue == KErrCancel || statusValue == KErrOverflow) + return; + + // Handle the timer expiry + ReactToSoundTimerExpiry(); + } + + +//************************************************************************************* +/** + * Replacement of CActive::Cancel function + */ +void CASSrvSoundController::Cancel() + { + // If we're currently timing an event, Cancel the timer and send + // a StopSound command. Otherwise, don't pollute the Alert Server + // with an extra message. + if( IsActive() ) + { + CTimer::Cancel(); + CancelAllSounds(); + } + } + + +// +// +// + + +//************************************************************************************* +void CASSrvSoundController::ReactToSoundTimerExpiry() + { + __ASSERT_DEBUG(!IsSoundControllerDisabled(), ASSrvStaticUtils::Panic(ASSrvStaticUtils::EASSrvPanicUnexpectedEventSoundControllerDisabled)); + + if (InQuietPeriod()) + { + // If we're in a quiet period, and the sound timer has expired, + // then we should now start to play sound for any currently notifying + // alarm. + iSoundControllerFlags.Clear(ESoundControllerFlagsInQuietPeriod); + ServerData().SoundSettings().SetGlobalSoundState(EAlarmGlobalSoundStateOn); + } + else if (SoundIsPausedForAlarm()) + { + iSoundControllerFlags.Clear(ESoundControllerFlagsPausing); + StartSoundTimingForAnyWaitingAlarmAfterPausingOrQuietPeriod(); + } + else + { + // ensure notifying Alarm hasn't been disabled, etc... between Timer expiry and this function running + if(IdOfNotifyingAlarm() != KNullAlarmId) + { + WorkOutAndScheduleForNextSoundCycle(); + } + else + { + // stop any sound currently playing + CancelAllSounds(); + } + } + } + + +//************************************************************************************* +void CASSrvSoundController::NotifySoundEvent(MASSrvSoundControllerObserver::TSoundControllerEvent aEvent, TAlarmId aAlarmId) + { + __ASSERT_DEBUG(!IsSoundControllerDisabled(), ASSrvStaticUtils::Panic(ASSrvStaticUtils::EASSrvPanicUnexpectedEventSoundControllerDisabled)); + + if (aEvent == MASSrvSoundControllerObserver::ESoundControllerEventPlaySound && + (InQuietPeriod() || SoundIsPausedForAlarm())) + { + // If we're in a quiet period, then we should ignore play sound requests... + return; + } + else if (ServerData().SoundSettings().GlobalSoundState() == EAlarmGlobalSoundStateOff) + { + // Also ignore sounds requests if all sounds are disabled + return; + } + + // In an effort to minimise the polution passed to the Alert Server, let's + // keep track of what message was last sent, except the SoundStopAll events. + if ( MASSrvSoundControllerObserver::ESoundControllerEventPlayNothing == aEvent && aAlarmId == KNullAlarmId ) + { + iObserver.MASHandleSoundControllerEvent(aEvent, aAlarmId); + } + else if ( iPreviousSoundEvent != aEvent ) + { + iPreviousSoundEvent = aEvent; + iObserver.MASHandleSoundControllerEvent(aEvent, aAlarmId); + } + } + + +//************************************************************************************* +void CASSrvSoundController::StartSoundTimingForAnyWaitingAlarmAfterPausingOrQuietPeriod() + { + __ASSERT_DEBUG(!IsSoundControllerDisabled(), ASSrvStaticUtils::Panic(ASSrvStaticUtils::EASSrvPanicUnexpectedEventSoundControllerDisabled)); + + const TAlarmId id = IdOfNotifyingAlarm(); + if (id != KNullAlarmId) + { + // Cancel function of this object is overridden and sends + // ESoundControllerEventPlayNothing message as well as Cancels the timer. + // The situation that the timer is pending is very unlikely, so we check + // if it is Active to prevent unnecessary message pollution. + if (IsActive()) + { + Cancel(); + } + + // Save the time we're supposed to wake up at and reset + // the alarm's sound state so that it can start over + // again when we're due to run. + TASSrvAlarm* alarm = ServerData().Queue().QueueAlarmById(id); + + // Clear the sound paused flag for the notifying alarm + alarm->ClearSoundPausedFlag(); + + // We have a new baseline for all sound timing calculations. The next set of + // cycle calcs must be based upon the time at which the quiet period ends + // rather than the original due time. + alarm->ReinitializeSoundState(iTimeToReturnToNormalSoundTimingBehaviour); + + // Is a fixed value, should not be recomputed if offset changes. + iSoundControllerFlags.Set(ESoundControllerFlagsIsFixed); + + // Run again straight away - this will start the sound again immediately. + AtUTC(iTimeToReturnToNormalSoundTimingBehaviour); + } + } + + +//************************************************************************************* +void CASSrvSoundController::WorkOutAndScheduleForNextSoundCycle() + { + __ASSERT_DEBUG(!IsSoundControllerDisabled(), ASSrvStaticUtils::Panic(ASSrvStaticUtils::EASSrvPanicUnexpectedEventSoundControllerDisabled)); + + __ASSERT_ALWAYS(IdOfNotifyingAlarm() != KNullAlarmId, ASSrvStaticUtils::Panic(ASSrvStaticUtils::EASSrvPanicNotifyingAboutWrongAlarm)); + + const TAlarmId id = IdOfNotifyingAlarm(); + + + // Get the alarm which we are monitoring... + TASSrvAlarm* alarm = ServerData().Queue().QueueAlarmById(id); + + + // Tell observer what to do + MASSrvSoundControllerObserver::TSoundControllerEvent event = MASSrvSoundControllerObserver::ESoundControllerEventPlaySound; + if (alarm->SoundState() == TASSrvAlarm::ESoundStatePlayingNothing) + event = MASSrvSoundControllerObserver::ESoundControllerEventPlayNothing; + NotifySoundEvent(event, id); + + // Work out when we're next due to play [sound/silence] + const TTime nextTimePeriod = alarm->CalculateAndPrepareNextSoundCycle(); + + if (nextTimePeriod == Time::NullTTime()) + { + // We've reached the end of the Sound Interval sequence, with the + // Sound Setting Repeat option set to EAlarmSoundRepeatSettingStop. + // Reset the Alarm Id and *don't* start the timer. + IdOfNotifyingAlarm() = KNullAlarmId; + } + else + { + // Toggle the alarm's sound state so that when we wake up, we do the right thing + alarm->ToggleSoundState(); + + // Next time to react is a fixed offset to UTC + iSoundControllerFlags.Set(ESoundControllerFlagsIsFixed); + + // Wait for the next time to react + AtUTC(nextTimePeriod); + } + } + + +/** +Silent sound if the given alarm id is the currently playing alarm. +Handles EASAltAlertServerResponseSilence. +*/ +void CASSrvSoundController::CancelSound(TAlarmId aAlarmId) + { + // Don't notify anyone if we're disabled. + if( IsSoundControllerDisabled() ) + { + return; + } + + if ( aAlarmId == KNullAlarmId ) + { + NotifySoundEvent(MASSrvSoundControllerObserver::ESoundControllerEventPlayNothing, aAlarmId); + } + else if ( aAlarmId == IdOfNotifyingAlarm() ) + { + NotifySoundEvent(MASSrvSoundControllerObserver::ESoundControllerEventPlayNothing, aAlarmId); + } + } + +/** +If there are other alarm in the notifying alarm list, start sound play +sequence for the previous notifying alarm. If we are in quite period, this previous +alarm will be played after quite period ends +*/ +void CASSrvSoundController::PlayPreviousNotifyingAlarm() + { + CASSrvAlarmQueue& queue = ServerData().Queue(); + + TASSrvAlarm* lastAlarm = NULL; + RASSrvIteratorByState iterator(queue, EAlarmStateNotifying); + iterator.Open(); + if (!iterator.NextAlarmAvailable()) + { + return; + } + + // look for a the latest alarm + while (iterator.NextAlarmAvailable()) + { + lastAlarm = &iterator.NextAlarm(); + } + + IdOfNotifyingAlarm() = lastAlarm->Id(); + + if (!InQuietPeriod()) + { + // reset the baseline for all sound timing calculations. + TTime now(ASSrvStaticUtils::UtcTimeNow()); + lastAlarm->ReinitializeSoundState(now); + WorkOutAndScheduleForNextSoundCycle(); + } + } +