diff -r 000000000000 -r 2e3d3ce01487 commonappservices/alarmserver/Server/Source/ASSrvAlarmQueue.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/commonappservices/alarmserver/Server/Source/ASSrvAlarmQueue.cpp Tue Feb 02 10:12:00 2010 +0200 @@ -0,0 +1,1604 @@ +// 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: Implementation of the class representing the Alarm Queue +// + +#include +#include "ASSrvAlarmQueue.h" + + +// User Includes +#include "ASSrvTimer.h" +#include "ASSrvStaticUtils.h" +#include "ASSrvServerWideData.h" +#include "ASSrvAnyEventManager.h" +#include "ASSrvIteratorByState.h" +#include "ASSrvIteratorByStatus.h" +#ifdef SYMBIAN_SKIPPED_CALENDAR_ALARMS +#include "ASSrvIteratorByCategory.h" +#endif +#ifdef SYMBIAN_SYSTEM_STATE_MANAGEMENT +#include "ASSrvIteratorByWakeup.h" +#endif +#include "ASSrvEnvironmentChangeManager.h" +#include "ASSrvSoundSettings.h" +#include "ASSrvDataPool.h" +#include "ASSrvDSTChange.h" + + +// Constants +const TInt KASSrvNumberOfDaysInOneWeek = 7; + +// Macro for security policy objects +static _LIT_SECURITY_POLICY_PASS(KReadPolicy); +static _LIT_SECURITY_POLICY_S0(KWritePolicy, 0x101f5027); + +// Enumerations +#define DEBUG_PRINT_QUEUE(a){ RDebug::Print(_L(a)); \ + for(TInt i=0; iConstructL(); + CleanupStack::Pop(self); + return self; + } + +/** + * @see MASSrvEnvironmentChangeObserver + */ +void CASSrvAlarmQueue::MEnvChangeHandleEvent(TInt aChanges, TUint aWorkdays, TBool aWorkdaysChanged) + { + // Locale changes can be triggered by a lot of reasons, process locale + // changes only due to utc offset change and workdays change. + if ( (aChanges & EChangesSystemTime) || aWorkdaysChanged || (aChanges & EChangesLocale && (iPreviousUtcOffset != User::UTCOffset())) ) + { +#ifdef SYMBIAN_SKIPPED_CALENDAR_ALARMS + TBool possibleHiddenFloatingAlarm(EFalse); + // Store the time of the first calendar alarm, as it may have been skipped + TTime firstSkippedAlarmLocalTime(0); + + const TAlarmId headCalendarAlarmId = HeadCalendarAlarmId(); + if (headCalendarAlarmId != KNullAlarmId) + { + const TASSrvAlarm* headCalendarAlarm = QueueAlarmById(headCalendarAlarmId); + firstSkippedAlarmLocalTime = headCalendarAlarm->NextDueTime(); + + // The calendar server adds a single alarm at a time. If this + // alarm is fixed, a skipped alarm instances notification must be + // sent, since the calendar server may have had other unqueued + // alarms that were skipped. + possibleHiddenFloatingAlarm = !headCalendarAlarm->IsFloating() && (headCalendarAlarm->NextDueTime() + iPreviousUtcOffset < ASSrvStaticUtils::LocalTimeNow()); + } + //Retrive the old local time before the time zone has been changed for the first skipped alarm. + firstSkippedAlarmLocalTime = firstSkippedAlarmLocalTime + ServerData().CachedUtcOffset(); + TBool skippedAgendaAlarm(EFalse); + + // Tell every alarm that the date/time has changed + const TInt count = QueueAlarmCount(); + + TRAP_IGNORE( + for (TInt i = count - 1; i >= 0; i--) + { + TASSrvAlarm& alarm = QueueAlarmAt(i); + if (alarm.HandleDateTimeChangedL(aWorkdays, aWorkdaysChanged)) + { + skippedAgendaAlarm = ETrue; + } + } + ) + + // If there was a skipped agenda alarm, publish the environment change and the details of the first + // skipped alarm, otherwise, if there was a UTC offset change, only publish the environment change + if (skippedAgendaAlarm) + { + PublishSkippedAlarm(ETrue); + PublishAlarmedInstanceParams(firstSkippedAlarmLocalTime, aChanges & EChangesSystemTime); + } + else if (possibleHiddenFloatingAlarm) + { + PublishAlarmedInstanceParams(firstSkippedAlarmLocalTime, aChanges & EChangesSystemTime); + } + else if (iPreviousUtcOffset != User::UTCOffset()) + { + PublishSkippedAlarm(EFalse); + } +#else + // Tell every alarm that the date/time has changed + const TInt count = QueueAlarmCount(); + + TBool skippedAgendaAlarm = EFalse; + TRAP_IGNORE( + for (TInt i = count - 1; i >= 0; i--) + { + TASSrvAlarm& alarm = QueueAlarmAt(i); + if (alarm.HandleDateTimeChangedL(aWorkdays, aWorkdaysChanged)) + { + skippedAgendaAlarm = ETrue; + } + } + ) + if (skippedAgendaAlarm || iPreviousUtcOffset != User::UTCOffset()) + { + PublishSkippedAlarm(skippedAgendaAlarm); + } +#endif + + //Re sort the alarms after the above date/time change + TLinearOrder order(ASSrvStaticUtils::CompareAlarms); + iAlarms.Sort(order); + + // Update floating alarms' due times. + UpdateFloatingDueTimes(); + + // When the date/time/locale changes we always simulate a change in the head item so that the timer will reset itself. + const TAlarmId newHeadItemId = HeadAlarmId(); + NotifyEvent(MASSrvAlarmQueueObserver::EASSrvAlarmQueueEventHeadItemChanged, newHeadItemId); + + // Global any event notification + ServerData().AnyEventManager().MASAnyEventManagerObserverNotifyChanges(EAlarmChangeEventSystemDateTimeChanged, KNullAlarmId); + + // Remove the dead alarms + RemoveDeadAlarms(); + + // And update the UTC offset + iPreviousUtcOffset = User::UTCOffset(); + } + + if (aChanges & EChangesMidnightCrossover) + { + //Tidy up alarm queue every midnight + RemoveDeadAlarms(); + } + } + +/** + * Request notification when the state or status of an alarm changes + */ +void CASSrvAlarmQueue::RequestAlarmObservationEventsL(MASSrvAlarmObserver& aObserver) + { + User::LeaveIfError(iAlarmObservers.Append(TASSrvAlarmObserverMapplet(aObserver))); + } + +/** + * Cancel a previous notification request + */ +void CASSrvAlarmQueue::RequestAlarmObservationEventsCancel(MASSrvAlarmObserver& aObserver) + { + const TInt count = iAlarmObservers.Count(); + for(TInt i=0; i order(ASSrvStaticUtils::CompareAlarms); + User::LeaveIfError(iAlarms.InsertInOrderAllowRepeats(alarm, order)); + CleanupStack::Pop(alarm); + + // Change the state to queued + TASSrvAlarm* newAlarm = QueueAlarmById(newId); + newAlarm->SetState(EAlarmStateQueued); + + // Notify we've added an alarm + NotifyEvent(MASSrvAlarmQueueObserver::EASSrvAlarmQueueEventAlarmAdded, newAlarm->Id()); + + // Did the head item change? + const TAlarmId newHeadItemId = HeadAlarmId(); + if (newHeadItemId != headItemId) + { + // Head item has changed. The timer will pick up the change and requeue itself + // for the new alarm. + NotifyEvent(MASSrvAlarmQueueObserver::EASSrvAlarmQueueEventHeadItemChanged, newHeadItemId); + } + + ServerData().AnyEventManager().MASAnyEventManagerObserverNotifyChanges(EAlarmChangeEventAlarmAddition, newId); + } + +/** + * Releases a previously allocated alarm. Removes it from the queue, and if this + * alarm was at the head of the queue, a new alarm is promoted. + */ +void CASSrvAlarmQueue::DeQueueAlarm(const TASSrvAlarm& aAlarm) + { + const TAlarmId id = aAlarm.Id(); + const TBool isHeadItem = (id == HeadAlarmId()); + + // Find index from alarm + TIdentityRelation identityRelation(ASSrvStaticUtils::CompareAlarmsExact); + const TInt errorOrIndex = iAlarms.Find(&aAlarm, identityRelation); + __ASSERT_ALWAYS(errorOrIndex != KErrNotFound, ASSrvStaticUtils::Fault(ASSrvStaticUtils::EASSrvFaultAlarmNotFound)); + + // Inform alarm it's being destroyed + TASSrvAlarm* alarm = &QueueAlarmAt(errorOrIndex); + __ASSERT_ALWAYS(alarm->Id() == aAlarm.Id(), ASSrvStaticUtils::Panic(ASSrvStaticUtils::EASSrvPanicAttemptingToDequeWrongAlarm)); + alarm->HandleDeQueue(); + + // DEBUG_PRINT_QUEUE("ALARMSERVER Before removing item") + + // Remove from queue +#ifdef _DEBUG + const TInt count = QueueAlarmCount(); +#endif + iAlarms.Remove(errorOrIndex); + delete alarm; + __ASSERT_DEBUG(iAlarms.Count() == count-1, ASSrvStaticUtils::Panic(ASSrvStaticUtils::EASSrvPanicAlarmNotDeQueued)); + + // DEBUG_PRINT_QUEUE("ALARMSERVER After removing item") + + // Notify we've deleted an alarm + NotifyEvent(MASSrvAlarmQueueObserver::EASSrvAlarmQueueEventAlarmDeleted, id); + + // DEBUG_PRINT_QUEUE("ALARMSERVER After notifying event") + + // Notify change + ServerData().AnyEventManager().MASAnyEventManagerObserverNotifyChanges(EAlarmChangeEventAlarmDeletion, id); + + // DEBUG_PRINT_QUEUE("ALARMSERVER After notifying any event") + + if (isHeadItem) + { + // Head item has changed. The timer will pick up the change and requeue itself + // for the new alarm. + const TAlarmId newHeadItem = HeadAlarmId(); + NotifyEvent(MASSrvAlarmQueueObserver::EASSrvAlarmQueueEventHeadItemChanged, newHeadItem); + + // DEBUG_PRINT_QUEUE("ALARMSERVER After head item changed") + } + } + +/** + * Returns the alarm at the head of the queue or NULL if there is none. + */ +const TASSrvAlarm* CASSrvAlarmQueue::HeadAlarm() const + { + // There is a head alarm if there is at least one alarm in the queue + // and that alarm is not disabled. + CASSrvAlarmQueue& self = const_cast(*this); + + // Create & open primary iterator + RASSrvIteratorByStatus primaryIterator(self, EAlarmStatusEnabled); + primaryIterator.Open(); + + // Create and attach secondary iterator + RASSrvIteratorByState secondaryIterator(self, EAlarmStateQueued, EAlarmStateSnoozed); + primaryIterator.IteratorAttach(secondaryIterator); + + if (primaryIterator.NextAlarmAvailable()) + { + return QueueAlarmById(primaryIterator.NextAlarm().Id()); + } + else + { + return NULL; + } + } + +#ifdef SYMBIAN_SKIPPED_CALENDAR_ALARMS +/** + * Returns the calendar alarm at the head of the queue + */ +TAlarmId CASSrvAlarmQueue::HeadCalendarAlarmId() const + { + CASSrvAlarmQueue& self = const_cast(*this); + + // Create & open primary iterator + RASSrvIteratorByStatus primaryIterator(self, EAlarmStatusEnabled); + primaryIterator.Open(); + + // Create and attach secondary iterator + RASSrvIteratorByState secondaryIterator(self, EAlarmStateQueued, EAlarmStateSnoozed); + primaryIterator.IteratorAttach(secondaryIterator); + + // The category for calendar alarms is defined in caalarm.h and is + // reproduced here to avoid a dependency on the calendar server + const TUid KUidAgendaModelAlarmCategory = { 0x101F4A70 }; + + // Create and attach tertiary iterator + RASSrvIteratorByCategory tertiaryIterator(self, KUidAgendaModelAlarmCategory); + primaryIterator.IteratorAttach(tertiaryIterator); + + TAlarmId returnAlarmId(KNullAlarmId); + + if (primaryIterator.NextAlarmAvailable()) + { + returnAlarmId = primaryIterator.NextAlarm().Id(); + } + + return returnAlarmId; + } +#endif + +#ifdef SYMBIAN_SYSTEM_STATE_MANAGEMENT +/** + * Returns the alarm id of the head wakeup alarm of the queue + */ +TAlarmId CASSrvAlarmQueue::HeadWakeupAlarmId() const + { + // There is a head alarm if there is at least one alarm in the queue + // and that alarm is not disabled. + CASSrvAlarmQueue& self = const_cast(*this); + + // Create & open primary iterator + RASSrvIteratorByStatus primaryIterator(self, EAlarmStatusEnabled); + primaryIterator.Open(); + + // Create and attach secondary iterator + RASSrvIteratorByState secondaryIterator(self, EAlarmStateQueued, EAlarmStateSnoozed); + primaryIterator.IteratorAttach(secondaryIterator); + + // Create and attach tertiary iterator + RASSrvIteratorByWakeup tertiaryIterator(self); + secondaryIterator.IteratorAttach(tertiaryIterator); + + TAlarmId returnAlarmId(KNullAlarmId); + + if (primaryIterator.NextAlarmAvailable()) + { + returnAlarmId = primaryIterator.NextAlarm().Id(); + } + + return returnAlarmId; + } +#endif + +/** + * Returns the id of the alarm at the head of the queue, or if there isn't a head alarm returns KNullAlarmId + */ +TAlarmId CASSrvAlarmQueue::HeadAlarmId() const + { + TAlarmId headItemId = KNullAlarmId; + const TASSrvAlarm* headAlarm = HeadAlarm(); + if (headAlarm) + { + headItemId = headAlarm->Id(); + } + return headItemId; + } + +/** + * Called whenever a session logs off. All session-specific alarm should be removed in this instance. + */ +void CASSrvAlarmQueue::RemoveAllSessionAlarmsBySessionId(TASSrvSessionId aSessionId) + { + const TInt count = QueueAlarmCount(); + for(TInt i=count-1; i>=0; i--) + { + const TASSrvAlarm& alarm = QueueAlarmAt(i); + if (alarm.OriginatingSessionId() == aSessionId && alarm.Characteristics().IsSet(EAlarmCharacteristicsSessionSpecific)) + DeQueueAlarm(alarm); + } + } + +/** + * Generates and returns the next valid alarm id. + */ +TAlarmId CASSrvAlarmQueue::NextFreeAlarmId() + { + if (iNextFreeAlarmId == KMaxTInt) + { + iNextFreeAlarmId = 1; // Zero is special - "No alarm id" + } + else + { + ++iNextFreeAlarmId; + } + + // Generate a unique alarm Id. + while(AlarmIdIsInUse(iNextFreeAlarmId)) + { + if (iNextFreeAlarmId == KMaxTInt) + { + iNextFreeAlarmId = 1; + } + else + { + ++iNextFreeAlarmId; + } + } + + return iNextFreeAlarmId; + } + +/** + * Specify type of Store (or Internalize) operation wanted: + * 1. Externalize + * - synchronous write of alarm queue and related information to file. + * 2. Internalize after Startup or Restore + * - synchronous read of alarm queue and related information from file. + * 3. Backup + */ +TInt CASSrvAlarmQueue::StartAlarmStoreOperation(TStoreOperation aStoreOperation) + { + TInt error = KErrNone; + + switch (aStoreOperation) + { + case EStoreInternalizeStartup: + { + DEBUG_PRINT1(_L("> StartAlarmStoreOperation (InternalizeStartup)")); + // Can't have two concurrent Store operations + if (iStoreOperation != EStoreIdle) + error = KErrLocked; + break; + } + case EStoreInternalizeRestore: + { + DEBUG_PRINT1(_L("> StartAlarmStoreOperation (InternalizeRestore)")); + // Check this is progression to Restore from Internalize + if (iStoreOperation != EStoreRestore) + error = KErrLocked; + break; + } + case EStoreExternalize: + { + DEBUG_PRINT1(_L("> StartAlarmStoreOperation (Externalize)")); + // Can't have two concurrent Store operations + if (iStoreOperation != EStoreIdle) + error = KErrLocked; + break; + } + case EStoreBackup: + { + DEBUG_PRINT1(_L("> StartAlarmStoreOperation (Backup)")); + // Can't have two concurrent Store operations + if (iStoreOperation != EStoreIdle) + error = KErrLocked; + break; + } + case EStoreRestore: + { + DEBUG_PRINT1(_L("> StartAlarmStoreOperation (Restore)")); + // Can't have two concurrent Store operations + if (iStoreOperation != EStoreIdle) + { + error = KErrLocked; + } + else + { + // notify clients that Restore has started + ServerData().AnyEventManager().MASAnyEventManagerObserverNotifyChanges(EAlarmChangeEventRestoreStarted, KNullAlarmId); + } + break; + } + case EStoreIdle: + default: + { + DEBUG_PRINT1(_L("> StartAlarmStoreOperation (Idle or other invalid)")); + error = KErrInUse; + __ASSERT_DEBUG(EFalse, ASSrvStaticUtils::Fault(ASSrvStaticUtils::EASSrvFaultStartInvalidAlarmStoreOperation)); + break; + } + } + if (!error) + { + iStoreOperation = aStoreOperation; + } + + DEBUG_PRINT2(_L("< StartAlarmStoreOperation result = %i"), error); + return error; + } + +#ifdef SYMBIAN_SYSTEM_STATE_MANAGEMENT +/** + * Handle a change to the next DST event + */ +void CASSrvAlarmQueue::HandleNextDSTChangeEventL() + { + UpdateRTC(); + } + +/** + * Find the head wakeup alarm and queue set it with the real-time-clock. + */ +void CASSrvAlarmQueue::UpdateRTC() + { + // We always call UnsetWakeupAlarm even if we are going to call SetWakeupAlarm later because the RTC + // adaptation plug-in requires UnsetWakeupAlarm to be called before any call to SetWakeupAlarm. + TRequestStatus status; + iSsmRtcAdaptation.UnsetWakeupAlarm(status); + User::WaitForRequest(status); + + // loop through the alarm queue until the first wakeup alarm is found + TAlarmId headWakeupAlarmId = HeadWakeupAlarmId(); + + DEBUG_PRINT2(_L("CASSrvAlarmQueue::UpdateRTC alarm count = %d"),QueueAlarmCount()); + + if (headWakeupAlarmId != KNullAlarmId) + { + // There is a wakeup alarm + const TASSrvAlarm* headWakeupAlarm = QueueAlarmById(headWakeupAlarmId); + TTime wakeupAlarmTime(headWakeupAlarm->NextDueTime()); + + if (headWakeupAlarm->IsFloating()) + { + TTime nextDSTChangeUTC = iASSrvDSTChange->NextDSTChangeUTC(); + + if (nextDSTChangeUTC != Time::NullTTime() && nextDSTChangeUTC < wakeupAlarmTime) + { + // The alarm is set to expire after a DST event so adjust the UTC time + // of the alarm to what it will be after the DST rollover + wakeupAlarmTime += iPreviousUtcOffset; + wakeupAlarmTime -= iASSrvDSTChange->NextUTCOffset(); + } + } + + // Set the key value to 'EWakeupAlarmSet' so that listeners are notified of the presence of an active wakeup alarm. + RProperty::Set(KAlarmServerPubSubCategory, KWakeupAlarmPubSubKey, EActiveWakeupAlarmSet ); + DEBUG_PRINT1(_L("Set the KWakeupAlarmPubSubKey to EActiveWakeupAlarmSet")); + + // Set the Real Time Clock in UTC + TRequestStatus status; + TPckgC wakeupAlarmTimePckg(wakeupAlarmTime); + iSsmRtcAdaptation.SetWakeupAlarm(wakeupAlarmTimePckg, status); + User::WaitForRequest(status); + } + else + { + // Notify the listeners that there is no head wake alarm present in the alarm queue + RProperty::Set(KAlarmServerPubSubCategory, KWakeupAlarmPubSubKey, EActiveNoWakeupAlarmsSet ); + DEBUG_PRINT1(_L("Set the KWakeupAlarmPubSubKey to EActiveNoWakeupAlarmsSet")); + } + + } +#endif + +/** + * End an Alarm Store Operation (Internalize, Externalize, Backup or Restore). + * Various tidying up including: + * Finalise an Internalise, or throw it away. (depending on aError) + * Process queued change events. + * Release the Access Token. + */ +void CASSrvAlarmQueue::EndAlarmStoreOperation(TInt aError) + { + switch (iStoreOperation) + { + case EStoreIdle: + default: + { + DEBUG_PRINT1(_L("> EndAlarmStoreOperation (EStoreIdle or invalid)")); + __ASSERT_DEBUG(EFalse, ASSrvStaticUtils::Fault(ASSrvStaticUtils::EASSrvFaultEndInvalidAlarmStoreOperation)); + break; + } + case EStoreInternalizeStartup: + case EStoreInternalizeRestore: + { + DEBUG_PRINT2(_L("> EndAlarmStoreOperation (InternalizeXXX, aError = %i)"), aError); + + if (!aError) + { + // Alarm Queue is about to change + // (stops Alarm Timer, currently notifying alarm, etc...) + NotifyEvent(MASSrvAlarmQueueObserver::EASSrvAlarmQueueEventAlarmStartInternalize, KNullAlarmId); + + // Apply Internalize buffers for Alarm Queue, Alarm Data & Sound Intervals + // Alarm Queue + ApplyInternalizedData(ETrue); + // Alarm Data Pool + ServerData().DataPool().ApplyInternalizedData(ETrue); + // Sound Settings + ServerData().SoundSettings().ApplyInternalizedData(ETrue); + + // Notify observers about new queue + const TAlarmId newHeadItemId = HeadAlarmId(); + if (newHeadItemId != KNullAlarmId) + { + // Head item has changed. The timer will pick up the change and requeue itself + // for the new alarm. + NotifyEvent(MASSrvAlarmQueueObserver::EASSrvAlarmQueueEventHeadItemChanged, newHeadItemId); + } + + // notify clients that Restore has completed + if (iStoreOperation == EStoreInternalizeRestore) + { + ServerData().AnyEventManager().MASAnyEventManagerObserverNotifyChanges(EAlarmChangeEventRestoreCompleted, KNullAlarmId); + } + +#ifdef SYMBIAN_SYSTEM_STATE_MANAGEMENT + + else if (iStoreOperation == EStoreInternalizeStartup) + { + // When internalize happens on alarm server start-up publish the information + // whether we have an active alarm set or not + DEBUG_PRINT1(_L("CASSrvAlarmQueue::EndAlarmStoreOperation Calls UpdateRTC after internalizing the queue on alarm server startup")); + UpdateRTC(); + } + +#endif + } // end of if block + else + { + // Discard Internalize buffers for Alarm Queue, Alarm Data & Sound Intervals + // Alarm Queue + ApplyInternalizedData(EFalse); + // Alarm Data Pool + ServerData().DataPool().ApplyInternalizedData(EFalse); + // Sound Settings + ServerData().SoundSettings().ApplyInternalizedData(EFalse); + + // notify clients that Restore has failed + if (iStoreOperation == EStoreInternalizeRestore) + { + ServerData().AnyEventManager().MASAnyEventManagerObserverNotifyChanges(EAlarmChangeEventRestoreFailed, KNullAlarmId); + } + } + break; + } + case EStoreExternalize: + { + DEBUG_PRINT1(_L("> EndAlarmStoreOperation (Externalize)")); + +#ifdef SYMBIAN_SYSTEM_STATE_MANAGEMENT + TRAP_IGNORE(UpdateRTC()); +#endif + break; + } + case EStoreBackup: + { + DEBUG_PRINT1(_L("> EndAlarmStoreOperation (Backup)")); + break; + } + case EStoreRestore: + { + DEBUG_PRINT1(_L("> EndAlarmStoreOperation (Restore)")); + // notify clients that Restore has failed + ServerData().AnyEventManager().MASAnyEventManagerObserverNotifyChanges(EAlarmChangeEventRestoreFailed, KNullAlarmId); + break; + } + } // end of switch-case block + +#ifdef SYMBIAN_SKIPPED_CALENDAR_ALARMS + + // Republish the last alarmed instance parameters after internalizing on startup + if (iStoreOperation == EStoreInternalizeStartup) + { + PublishAlarmedInstanceParams(iLastAlarmedInstanceParams); + } + +#endif + + iStoreOperation = EStoreIdle; + DEBUG_PRINT1(_L("< EndAlarmStoreOperation completed")); + return; + } + +/** + * Check that Alarm and Alarm Data is writable. + * Policy is to leave with KErrLocked if Restore is in progress. + */ +void CASSrvAlarmQueue::CheckAlarmQueueWritableL() + { + if (iStoreOperation == EStoreRestore +#ifdef SYMBIAN_SYSTEM_STATE_MANAGEMENT +|| iServerWideData.ServerIsReadOnly() +#endif + ) + { + User::Leave(KErrLocked); + } + } + +/** + * Restore the queue from the specified stream. + */ +void CASSrvAlarmQueue::InternalizeL(RReadStream& aStream) + { + // First read the leading count which indicates how many alarms there + // are. + const TInt count = aStream.ReadInt32L(); + + // Read in the stream + TLinearOrder order(ASSrvStaticUtils::CompareAlarms); + + if (iInternalizeAlarmQueue.Count() != 0) + { + iInternalizeAlarmQueue.ResetAndDestroy(); + } + + TTimeIntervalSeconds alarmExpireWindow = 0; + + // what type of Internalize are we doing? + switch (iStoreOperation) + { + case EStoreInternalizeStartup: // system startup + { + // 59s window for alarms in the past + alarmExpireWindow = KAlarmServerStartupExpireWindowInSeconds; + break; + } + case EStoreInternalizeRestore: // after a backup restore + { + // 0 window for alarms in the past + // NB Notified Alarms stay Notified, + // but Notifying Alarms become Notified + // This is reasonable as a Restore of alarms is a dramatic thing to do. + alarmExpireWindow = 0; + break; + } + default: + { + __ASSERT_ALWAYS(EFalse, ASSrvStaticUtils::Panic(ASSrvStaticUtils::EASSrvPanicInternalizeTypeInvalid)); + break; + } + } + + RPointerArray alarmArray; + CleanupResetAndDestroyPushL(alarmArray); + + for(TInt j=0; j> *alarm; + alarmArray.AppendL(alarm); + CleanupStack::Pop(); + } + + // Read in the next valid id + iInternalizeNextFreeAlarmId = aStream.ReadInt32L(); + + // Internalize the UTC offset the last time the queue was run. + TInt32 tempInt; + aStream >> tempInt; + TTimeIntervalSeconds currentOffset = User::UTCOffset(); + TTimeIntervalSeconds cachedOffset(tempInt); + + TTime previousDSTChangeUTC = iASSrvDSTChange->PreviousDSTChangeUTC(); + TTimeIntervalMinutes previousUTCOffset = iASSrvDSTChange->PreviousUTCOffset(); + TTime localTimeBeforeDSTChange = previousDSTChangeUTC + previousUTCOffset; + TTime localTimeAfterDSTChange = previousDSTChangeUTC + currentOffset; + + for(TInt i=0; iId(); + alarm->Id()=KNullAlarmId; + + // Validate the alarm, retaining the alarm status (enabled or disabled) + // - we only add valid alarms to the queue. + const TBool KAllowBlanketInThePastOnceOnlyAlarms = EFalse; // Default value + const TBool KEnableAlarm = alarm->Status()==EAlarmStatusEnabled; + //An alarm set to 'repeat next 24 hour' can be missed while the alarm server is inactive. + //Therefore it should be changed to 'repeat once' before it is restored to the alarm queue + //so that it is not incorrectly resheduled to notify in the next 24 hours + if(alarm->RepeatDefinition() == EAlarmRepeatDefintionRepeatNext24Hours) + { + alarm->RepeatDefinition() = EAlarmRepeatDefintionRepeatOnce; + } + + TTime previousAlarmLocalTime = alarm->NextDueTime() + previousUTCOffset; + TBool alarmInDSTChangeGap = (previousDSTChangeUTC != Time::NullTTime() // make sure DST change info has been published + && (previousAlarmLocalTime >= localTimeBeforeDSTChange && previousAlarmLocalTime < localTimeAfterDSTChange ) ); + + TTimeIntervalSeconds missingTime; + localTimeAfterDSTChange.SecondsFrom(localTimeBeforeDSTChange, missingTime); + + // Increase the allowable window if this alarm was due to go off in the DST change gap + TTimeIntervalSeconds thisAlarmsExpireWindow = alarmInDSTChangeGap ? alarmExpireWindow.Int() + missingTime.Int() : alarmExpireWindow; + + // Update the due time of floating alarms during a DST event. This will ensure that alarms + // are not invalidated by ValidateAndEnable(). + TTimeIntervalSeconds offsetDifference; + + if(alarm->IsFloating()) + { + if (cachedOffset != currentOffset) + { + // Calculate the change in the offset. + offsetDifference = (currentOffset.Int() - cachedOffset.Int()); + alarm->NextDueTime() -= offsetDifference; + alarm->OriginalExpiryTime() -= offsetDifference; + } + } + + const TInt error = alarm->ValidateAndEnable(thisAlarmsExpireWindow, KAllowBlanketInThePastOnceOnlyAlarms, KEnableAlarm); + + if (alarmInDSTChangeGap) + { + // The local alarm time was set during the missing DST changeover gap + // this repeat should actually appear an hour later than + alarm->NextDueTime() += missingTime; + } + + if (error != KErrArgument) + { + // Alarm is okay, so insert it into the queue in the + // right position + alarm->Id()=originalId; + const TInt error = iInternalizeAlarmQueue.InsertInOrderAllowRepeats(alarm, order); + User::LeaveIfError(error); + alarmArray[i] = NULL; + } + else + { + delete alarmArray[i]; + alarmArray[i] = NULL; + } + } + CleanupStack::PopAndDestroy(); //alarmArray + ServerData().CachedUtcOffset() = currentOffset; + } + +/** + * Whether to use the Internalize buffer, or throw it away + */ +void CASSrvAlarmQueue::ApplyInternalizedData(TBool aUseNewData) + { + DEBUG_PRINT2(_L("** ApplyInternalizedData(%u)"), aUseNewData); + + if (aUseNewData) + { + // Internalize Success + + // Replace all alarms with those from the stream + // Assign new queue to replace old one + ReplaceQueueWithInternalizedQueue(); + + iNextFreeAlarmId = iInternalizeNextFreeAlarmId; + + // Remove any which are too old + RemoveDeadAlarms(); + // Check whether the UTC offset has changed since the last time the Alarm Server + // was running, and update floating alarm due times if it has. + UpdateFloatingDueTimes(); + } + else + { + // Internalize Failure + iInternalizeAlarmQueue.ResetAndDestroy(); + + // Set the previous UTC offset to the current UTC offset, since there was no + // previous offset internalised. + ServerData().CachedUtcOffset() = User::UTCOffset(); + } + } + +/** + * Update the due times of floating alarms due to a locale or DST change. + * + * @internalComponent + */ +void CASSrvAlarmQueue::UpdateFloatingDueTimes() + { + // Has the UTC offset changed due to a Locale change? + TTimeIntervalSeconds offset = User::UTCOffset(); + + if (ServerData().CachedUtcOffset() != offset) + { + // Update the locally set alarm due times because the UTC offset has changed. + + // Calculate the change in the offset. + TTimeIntervalSeconds offsetDifference(offset.Int() - ServerData().CachedUtcOffset().Int()); + + // Get the head id so that we know if the queue has changed + const TAlarmId headItemId = HeadAlarmId(); + + // Cycle through all alarms. + TInt count = QueueAlarmCount(); + TBool orderChanged = EFalse; + + for (TInt i = 0; i < count; i++) + { + TASSrvAlarm& alarm = QueueAlarmAt(i); + + // Check whether the alarm is floating. + if (alarm.IsFloating()) + { + // Update the due time of the alarm, since it is floating. + TTime oldDueTime = alarm.NextDueTime(); + + // Subtract the change in offset from the due time to get the new due time. + TTime newDueTime = oldDueTime - offsetDifference; + + // Set the new due time of the alarm. + alarm.NextDueTime()=newDueTime; + + // Also, shift the original expiry time, to ensure that repeating + // alarms repeat correctly. + newDueTime = alarm.OriginalExpiryTime() - offsetDifference; + + // Shift the original ExpiryTime. + alarm.OriginalExpiryTime()=newDueTime; + orderChanged = ETrue; + } + } + + // Check whether the order of the alarm queue may have changed. + if (orderChanged) + { + // Re-order the alarm queue. + TLinearOrder order(ASSrvStaticUtils::CompareAlarms); + iAlarms.Sort(order); + + // Did the head item change? + const TAlarmId newHeadItemId = HeadAlarmId(); + + if (newHeadItemId != headItemId) + { + // Head item has changed. The timer will pick up the change and requeue itself + // for the new alarm. + NotifyEvent(MASSrvAlarmQueueObserver::EASSrvAlarmQueueEventHeadItemChanged, newHeadItemId); + } + } + + // Set the previous UTC offset to the current UTC offset. + ServerData().CachedUtcOffset() = offset; + } + } + +/** + * Publish skipped alarm data. + * + * @internalComponent + */ +void CASSrvAlarmQueue::PublishSkippedAlarm(TBool aSkippedCalAlarm) + { + // Define the property before use + TInt err = RProperty::Define(KAlarmServerPubSubCategory, KMissingAlarmPubSubKey, RProperty::EByteArray, KReadPolicy, KWritePolicy); + + // If the define completed successfully, create the data and publish + if (err == KErrNone || err == KErrAlreadyExists) + { + TMissedAlarmPubSubData pubSubData; + pubSubData.iTimeOfChangeUtc.UniversalTime(); + if(aSkippedCalAlarm) + { + // There are skipped alarms after a system time or time zone change + pubSubData.iValue = 2; + } + else + { + // There was a time zone change, but no alarms were skipped + pubSubData.iValue = 1; + } + + TPckgBuf pubSubBuf(pubSubData); + RProperty::Set(KAlarmServerPubSubCategory, KMissingAlarmPubSubKey, pubSubBuf); + } + } + +#ifdef SYMBIAN_SKIPPED_CALENDAR_ALARMS + +/** + * Publish data about the skipped alarm. + * + * @internalComponent + */ +void CASSrvAlarmQueue::PublishAlarmedInstanceParams(const TTime& aFirstSkippedAlarmLocalTime, TBool aSystemTimeChange) + { + // Create the data structure before publishing + TASShdAlarmedInstanceParams alarmedInstanceParams; + alarmedInstanceParams.iLocalStartTime = aFirstSkippedAlarmLocalTime; + alarmedInstanceParams.iLocalEndTime = ASSrvStaticUtils::LocalTimeNow(); + alarmedInstanceParams.iTimeType = aSystemTimeChange ? EFloatingOrFixed : EFloating; + + PublishAlarmedInstanceParams(alarmedInstanceParams); + } + +/** + * Publish data about the skipped alarm. + * + * @internalComponent + */ +void CASSrvAlarmQueue::PublishAlarmedInstanceParams(const TASShdAlarmedInstanceParams& aAlarmedInstanceParams) + { + // Define the property before use + TInt err = RProperty::Define(KAlarmServerPubSubCategory, KSkippedAlarmInstancesPubSubKey, RProperty::EByteArray, KReadPolicy, KWritePolicy); + + // If the define completed successfully, publish + if (err == KErrNone || err == KErrAlreadyExists) + { + TPckgBuf pubSubBuf(aAlarmedInstanceParams); + RProperty::Set(KAlarmServerPubSubCategory, KSkippedAlarmInstancesPubSubKey, pubSubBuf); + iLastAlarmedInstanceParams = aAlarmedInstanceParams; + } + } +#endif + +/** + * Store the queue to the specified stream. + */ +void CASSrvAlarmQueue::ExternalizeL(RWriteStream& aStream) const + { + const TInt count = QueueAlarmCount(); + aStream.WriteInt32L(count); + // + for(TInt i=0; i paramsBuf(iLastAlarmedInstanceParams); + aStream << paramsBuf; + + // Write two words reserved for future use + aStream.WriteInt32L(0); + aStream.WriteInt32L(0); + } + +/** + * Internalize the last published skipped agenda alarm data + */ +void CASSrvAlarmQueue::InternalizeLastAlarmedInstanceParamsL(RReadStream& aStream) + { + TPckgBuf paramsBuf; + aStream >> paramsBuf; + iLastAlarmedInstanceParams = paramsBuf(); + + // Read two words reserved for future use + aStream.ReadInt32L(); + aStream.ReadInt32L(); + } +#endif + +/** + * Called when the status of one or more alarms changes + */ +void CASSrvAlarmQueue::HandleAlarmStatusChanged(TAlarmId aAlarmThatChangedStatus, TAlarmStatus aOldStatus) + { + // Resort the queue. We need to do this for alarms which may, for example, + // have a repeat definition and was disabled and has been re-enabled but the + // original expiry time has since passed. The alarm's next due time has + // been updated but the queue does not yet reflect this. + TLinearOrder order(ASSrvStaticUtils::CompareAlarms); + iAlarms.Sort(order); + + // Called whenever an alarm changes state. We need to update the alarm timer. + const TASSrvAlarm* alarm = QueueAlarmById(aAlarmThatChangedStatus); + // + const TAlarmId newHeadAlarmId = HeadAlarmId(); + const TAlarmId timerAlarmId = ServerData().Timer().NextDueAlarmId(); + // + if (timerAlarmId != newHeadAlarmId || aAlarmThatChangedStatus == timerAlarmId) + { + // The head item has changed. + NotifyEvent(MASSrvAlarmQueueObserver::EASSrvAlarmQueueEventHeadItemChanged, newHeadAlarmId); + } + + // Notify observers + NotifyEvent(MASSrvAlarmQueueObserver::EASSrvAlarmQueueEventAlarmChanged, aAlarmThatChangedStatus); + NotifyAlarmObserverEvent(MASSrvAlarmObserver::EAlarmObserverStatusChanged, *alarm, static_cast(aOldStatus)); + + // Notify change + ServerData().AnyEventManager().MASAnyEventManagerObserverNotifyChanges(EAlarmChangeEventStatus, aAlarmThatChangedStatus); + } + +/** + * Called when the state of one or more alarms changes + */ +void CASSrvAlarmQueue::HandleAlarmStateChanged(TAlarmId aAlarmThatChangedState, TAlarmState aOldState) + { + // Resort the queue. We need to do this for alarms which have now become snoozed or + // alarms which were notified but are now re-queued because of their repeat definitions. + TLinearOrder order(ASSrvStaticUtils::CompareAlarms); + iAlarms.Sort(order); + + // We need to update the alarm timer if the head alarm is different to + // the old one. + CASSrvAlarmTimer& timer = ServerData().Timer(); + // + const TAlarmId newHeadAlarmId = HeadAlarmId(); + const TAlarmId timerAlarmId = timer.NextDueAlarmId(); + // + if (timerAlarmId != newHeadAlarmId) + { + // The head item has changed. + NotifyEvent(MASSrvAlarmQueueObserver::EASSrvAlarmQueueEventHeadItemChanged, newHeadAlarmId); + } + else if (newHeadAlarmId != KNullAlarmId) + { + // It is possible for the head alarm to change it's due time. + // In this case, the head alarm id is the same, but the times + // are different. + const TASSrvAlarm* newHeadAlarm = QueueAlarmById(newHeadAlarmId); + const TTime& timerDueTime = timer.NextDueAlarmOriginalExpiryTime(); + if (timerAlarmId == newHeadAlarmId && timerDueTime != newHeadAlarm->NextDueTime()) + { + // Alarm id hasn't changed, but the due time has + NotifyEvent(MASSrvAlarmQueueObserver::EASSrvAlarmQueueEventHeadItemChanged, newHeadAlarmId); + } + } + + // Notify observers + NotifyEvent(MASSrvAlarmQueueObserver::EASSrvAlarmQueueEventAlarmChanged, aAlarmThatChangedState); + NotifyAlarmObserverEvent(MASSrvAlarmObserver::EAlarmObserverStateChanged, *QueueAlarmById(aAlarmThatChangedState), static_cast(aOldState)); + + // Notify change + ServerData().AnyEventManager().MASAnyEventManagerObserverNotifyChanges(EAlarmChangeEventState, aAlarmThatChangedState); + } + +/** + * Called when the state of one or more alarms changes + */ +void CASSrvAlarmQueue::HandleAlarmCharacteristicsChanged(TAlarmId aAlarmThatChangedCharacteristics, TAlarmCharacteristicsFlags /*aOldCharacteristics*/) + { + // Notify observers + NotifyEvent(MASSrvAlarmQueueObserver::EASSrvAlarmQueueEventAlarmChanged, aAlarmThatChangedCharacteristics); + /** + * Alarm Observers don't currently need to know about this change. + * If this changes this is the place to do something like: + * NotifyAlarmObserverEvent(MASSrvAlarmObserver::EAlarmObserverCharacteristicsChanged, + * QueueAlarmById(aAlarmThatChangedCharacteristics), + * static_cast(aOldCharacteristics.Value())); + */ + + // Notify change + ServerData().AnyEventManager().MASAnyEventManagerObserverNotifyChanges(EAlarmChangeEventCharacteristics, aAlarmThatChangedCharacteristics); + } + +#ifdef SYMBIAN_SYSTEM_STATE_MANAGEMENT +void CASSrvAlarmQueue::HandleWakeupChanged(TAlarmId aAlarmThatChangedWakeup) + { + // Notify observers + NotifyEvent(MASSrvAlarmQueueObserver::EASSrvAlarmQueueEventAlarmChanged, aAlarmThatChangedWakeup); + } +#endif + +#ifdef SYMBIAN_ALARM_REPEAT_EXTENSIONS +void CASSrvAlarmQueue::HandleAlarmDaysChanged(TAlarmId aAlarmThatChangedDays) + { + // Check that the alarm has been queued (which we infer by the alarm having an id). + if (aAlarmThatChangedDays == KNullAlarmId) + { + return; + } + + // Resort the queue. + TLinearOrder order(ASSrvStaticUtils::CompareAlarms); + iAlarms.Sort(order); + + // Get the id of the head alarm. + const TAlarmId headAlarmId = HeadAlarmId(); + + // Get the id of the alarm associated with the alarm timer. + CASSrvAlarmTimer& timer = ServerData().Timer(); + const TAlarmId timerAlarmId = timer.NextDueAlarmId(); + + // Assume the head item will not change. + TBool headItemChanged = EFalse; + + if (headAlarmId != timerAlarmId) + { + headItemChanged = ETrue; + } + else + { + if (headAlarmId != KNullAlarmId) + { + const TASSrvAlarm* headAlarm = QueueAlarmById(headAlarmId); + const TTime& timerDueTime = timer.NextDueAlarmOriginalExpiryTime(); + + // It is possible for the head alarm to change its due time. In + // this case the alarm timer will no longer be correct. + if (headAlarm->NextDueTime() != timerDueTime) + { + headItemChanged = ETrue; + } + } + } + + if (headItemChanged) + { + NotifyEvent(MASSrvAlarmQueueObserver::EASSrvAlarmQueueEventHeadItemChanged, headAlarmId); + } + } +#endif + +void CASSrvAlarmQueue::HandleAlarmDataChanged(TAlarmId aAlarmThatChangedData) + { + // Notify observers + NotifyEvent(MASSrvAlarmQueueObserver::EASSrvAlarmQueueEventAlarmChanged, aAlarmThatChangedData); + } + +/** + * Returns a boolean indicating whether or not the specified alarm id + * is currently being used by another alarm. + */ +TBool CASSrvAlarmQueue::AlarmIdIsInUse(TAlarmId aAlarmId) const + { + const TInt count = QueueAlarmCount(); + for(TInt i=0; i=0; i--) + { + TASSrvAlarm& alarm = QueueAlarmAt(i); + const TTimeIntervalDays daysSinceLastExpired(alarm.NextDueTime().DaysFrom(timeNow)); + if (alarm.State() == EAlarmStateNotified && Abs(daysSinceLastExpired.Int()) >= KASSrvNumberOfDaysInOneWeek) + alarm.DeQueue(); + } + } + +/** + * Notify all alarm observers about the specified event. + */ +void CASSrvAlarmQueue::NotifyAlarmObserverEvent(MASSrvAlarmObserver::TObserverEvent aEvent, const TASSrvAlarm& aAlarm, TInt aEventSpecificData) + { + const TInt count = iAlarmObservers.Count(); + for(TInt i=0; iId() == aId) + return iAlarms[i]; + } + + return NULL; + } + +/** + * Returns a constant reference to a real alarm with the specified id. + */ +const TASSrvAlarm* CASSrvAlarmQueue::QueueAlarmById(TAlarmId aId) const + { + const TInt count = iAlarms.Count(); + for(TInt i=0; iId() == aId) + return iAlarms[i]; + } + + return NULL; + } + +/** + * Returns a reference to a real alarm with the specified id. + */ +TASSrvAlarm& CASSrvAlarmQueue::QueueAlarmByIdL(TAlarmId aId) + { + TASSrvAlarm* alarm = QueueAlarmById(aId); + if (!alarm) + { + User::Leave(KErrNotFound); + } + return *alarm; + } + +/** + * Returns a constant reference to a real alarm with the specified id. + */ +const TASSrvAlarm& CASSrvAlarmQueue::QueueAlarmByIdL(TAlarmId aId) const + { + const TASSrvAlarm* alarm = QueueAlarmById(aId); + if (!alarm) + { + User::Leave(KErrNotFound); + } + return *alarm; + } + +/** + * Notify observers about an event + */ +void CASSrvAlarmQueue::NotifyEvent(MASSrvAlarmQueueObserver::TASSrvAlarmQueueEvent aEvent, TAlarmId aAlarmId) + { + const TInt count = iNotificationList.Count(); + for(TInt i=0; iMAlarmQueueObserverHandleEvent(aEvent, aAlarmId); + } + } + +/** + * Replace one queue with another + */ +void CASSrvAlarmQueue::ReplaceQueueWithInternalizedQueue() + { + const TInt count = iAlarms.Count(); + for(TInt i=0; iCancelSessionAlarm(); + } + + // free memory used by old queue + iAlarms.ResetAndDestroy(); + + // assign from new buffer + iAlarms = iInternalizeAlarmQueue; + + // re-initialise source pointer array, by re-running its constructor + new(&iInternalizeAlarmQueue) RPointerArray; + } + +/** + * Returns ETrue if there is at least one alarm in the "waiting to notify + * state" + */ +TBool CASSrvAlarmQueue::HaveAdditionalAlarmsToNotify() + { + // Create & open primary iterator + RASSrvIteratorByState primaryIterator(*this, EAlarmStateWaitingToNotify); + primaryIterator.Open(); + + // Create and attach secondary iterator + RASSrvIteratorByStatus secondaryIterator(*this, EAlarmStatusEnabled); + primaryIterator.IteratorAttach(secondaryIterator); + + return primaryIterator.NextAlarmAvailable(); + } + +/** + * Returns the number of alarms which are enabled and in the "waiting to + * notify state" + */ +TInt CASSrvAlarmQueue::NumberOfAlarmsPendingNotification() + { + // Create & open primary iterator + RASSrvIteratorByState primaryIterator(*this, EAlarmStateWaitingToNotify); + primaryIterator.Open(); + + // Create and attach secondary iterator + RASSrvIteratorByStatus secondaryIterator(*this, EAlarmStatusEnabled); + primaryIterator.IteratorAttach(secondaryIterator); + + TInt count = 0; + while(primaryIterator.NextAlarmAvailable()) + { + primaryIterator.NextAlarm(); + ++count; + } + + return count; + } + +/** + * Return a reference to the alarm which is next awaiting notification + */ +TASSrvAlarm& CASSrvAlarmQueue::NextAlarmWaitingForNotification() + { + // Create & open primary iterator + RASSrvIteratorByState primaryIterator(*this, EAlarmStateWaitingToNotify); + primaryIterator.Open(); + + // Create and attach secondary iterator + RASSrvIteratorByStatus secondaryIterator(*this, EAlarmStatusEnabled); + primaryIterator.IteratorAttach(secondaryIterator); + + // The most recent alarm is at the head of the queue. + // This will be the next one awaiting notification. + TAlarmId idOfFirstAlarm = KNullAlarmId; + if(primaryIterator.NextAlarmAvailable()) + { + idOfFirstAlarm = primaryIterator.NextAlarm().Id(); + } + + __ASSERT_ALWAYS(idOfFirstAlarm != KNullAlarmId, ASSrvStaticUtils::Panic(ASSrvStaticUtils::EASSrvPanicIteratorAlarmIdNull)); + return *QueueAlarmById(idOfFirstAlarm); + }